├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── README.zh-CN.md ├── bali.toml ├── cmd ├── README.md └── bali │ ├── command_build.go │ ├── command_clean.go │ ├── command_update.go │ ├── crate.toml │ ├── main.go │ ├── res │ ├── bali.ico │ ├── chili.png │ └── chili.svg │ ├── windows.go │ └── winres.toml ├── docs ├── bali.docker.json ├── spec.md ├── unix.md └── windows.md ├── go.mod ├── go.sum ├── makeico └── ico.go ├── module ├── goversioninfo │ ├── LICENSE │ ├── README.md │ ├── VERSION │ ├── goversioninfo.go │ ├── icon.go │ ├── lang_cs.go │ └── structbuild.go └── rsrc │ ├── .hgignore │ ├── .travis.yml │ ├── AUTHORS │ ├── LICENSE.txt │ ├── README.txt │ ├── VERSION │ ├── binutil │ ├── plain.go │ ├── sizedfile.go │ ├── walk.go │ └── writer.go │ ├── buildcross.bat │ ├── coff │ └── coff.go │ ├── ico │ └── ico.go │ ├── internal │ └── write.go │ ├── rsrc │ └── rsrc.go │ └── testdata │ ├── akavel.ico │ ├── manifest.xml │ ├── syncthing.ico │ └── tmp.go ├── pkg ├── README.md └── barrow │ ├── barrow.go │ ├── barrow_test.go │ ├── cleanup.go │ ├── crate.go │ ├── misc.go │ ├── nfpm.go │ ├── package.go │ ├── resources │ └── template.sh │ ├── rpm.go │ ├── tar.go │ ├── vcs.go │ ├── version.go │ └── zip.go ├── script ├── bootstrap.bat ├── bootstrap.ps1 └── bootstrap.sh └── test ├── ReadMe.md ├── args └── args.go ├── cli.sh ├── ico ├── circular-chart-128.png ├── circular-chart-16.png ├── circular-chart-24.png ├── circular-chart-256.png ├── circular-chart-32.png ├── circular-chart-64.png ├── circular-chart.png ├── circular-chart.png.ico └── ico.go ├── rpmsimple └── main.go └── xz └── xz.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | paths-ignore: 5 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions 6 | - "docs/**" 7 | - "**.md" 8 | - "**.txt" 9 | - "LICENSE" 10 | pull_request: 11 | paths-ignore: 12 | - "docs/**" 13 | - "**.md" 14 | - "**.txt" 15 | - "LICENSE" 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | platform: [ubuntu-latest, macos-latest, windows-latest] 21 | include: 22 | - platform: ubuntu-latest 23 | bootstrap_script: ./script/bootstrap.sh 24 | - platform: macos-latest 25 | bootstrap_script: ./script/bootstrap.sh 26 | - platform: windows-latest 27 | bootstrap_script: script/bootstrap.bat 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 1 33 | - name: Setup go 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: 'stable' 37 | - name: Bootstrap Bali 38 | run: ${{ matrix.bootstrap_script }} 39 | - name: Upload release 40 | uses: svenstaro/upload-release-action@v2 41 | if: startsWith(github.ref, 'refs/tags/') 42 | with: 43 | file_glob: true 44 | file: out/* 45 | tag: ${{ github.ref }} 46 | repo_token: ${{ secrets.GITHUB_TOKEN }} 47 | overwrite: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | *.syso 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | /build/ 18 | *.tar.gz 19 | *.zip 20 | /*.sh 21 | cmd/bali/bali 22 | *.rpm 23 | /out/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (©) 2024 Bali contributors 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bali - Minimalist Golang build and packaging tool 2 | 3 | [![Master Branch Status](https://github.com/balibuild/bali/workflows/CI/badge.svg)](https://github.com/balibuild/bali/actions) 4 | 5 | 6 | [简体中文](./README.zh-CN.md) 7 | 8 | Bali is a *minimal Golang build and packaging tool* developed using Golang. 9 | 10 | ## Feature 11 | 12 | Bali has some functions that I think are useful: 13 | 14 | + Build parameters support derivation of environment variables 15 | + Package, create compressed package, support `rpm`, `tar`, `zip`, `sh`. 16 | + The Windows platform supports embedded version information, icons, and application manifest. 17 | 18 | rpm supported compression: 19 | + gzip 20 | + zstd 21 | + lzma 22 | + xz 23 | 24 | tar supported compression: 25 | + none --> pure tar 26 | + gzip --> tar.gz 27 | + zstd --> tar.zst 28 | + xz --> tar.xz 29 | + bzip2 --> tar.bz2 30 | + brotli --> tar.br 31 | 32 | sh supported compression: 33 | + none --> pure tar 34 | + gzip --> tar.gz 35 | + zstd --> tar.zst 36 | + xz --> tar.xz 37 | + bzip2 --> tar.bz2 38 | 39 | zip supported compression: 40 | + deflate 41 | + zstd 42 | + bzip2 43 | + xz 44 | 45 | 46 | Bali's command line help information is as follows: 47 | 48 | ```txt 49 | Usage: bali [flags] 50 | 51 | Bali - Minimalist Golang build and packaging tool 52 | 53 | Flags: 54 | -h, --help Show context-sensitive help. 55 | -M, --module="." Explicitly specify a module directory 56 | -B, --build="build" Explicitly specify a build directory 57 | -V, --verbose Make the operation more talkative 58 | -v, --version Print version information and quit 59 | 60 | Commands: 61 | build Compile the current module (default) 62 | update Update dependencies as recorded in the go.mod 63 | clean Remove generated artifacts 64 | 65 | Run "bali --help" for more information on a command. 66 | 67 | 68 | ``` 69 | 70 | bali build command: 71 | 72 | ```txt 73 | Usage: bali build [flags] 74 | 75 | Compile the current module (default) 76 | 77 | Flags: 78 | -h, --help Show context-sensitive help. 79 | -M, --module="." Explicitly specify a module directory 80 | -B, --build="build" Explicitly specify a build directory 81 | -V, --verbose Make the operation more talkative 82 | -v, --version Print version information and quit 83 | 84 | -T, --target="windows" Target OS for which the code is compiled 85 | -A, --arch="amd64" Target architecture for which the code is compiled 86 | --release=STRING Specifies the rpm package tag version 87 | -D, --destination="dest" Specify the package save destination 88 | --pack=PACK,... Packaged in a specific format. supported: zip, 89 | tar, sh, rpm 90 | --compression=STRING Specifies the compression method 91 | ``` 92 | 93 | 94 | ## Instructions 95 | 96 | Common build: 97 | 98 | ```shell 99 | cd /path/to/project 100 | bali 101 | ``` 102 | 103 | Create `Tar.gz` compressed package: 104 | 105 | ```shell 106 | bali --pack=tar 107 | ``` 108 | 109 | Create `STGZ` installation package, mainly used on Linux/macOS platform: 110 | 111 | ```shell 112 | bali --pack=sh --target=linux --arch=amd64 113 | ``` 114 | 115 | Output the installation package to the specified directory: 116 | 117 | ```shell 118 | bali --pack=rpm --target=linux --arch=amd64 --dest=/tmp/output 119 | ``` 120 | 121 | Create multiple packages at once: 122 | 123 | ```shell 124 | bali --target=linux --arch=arm64 '--pack=sh,rpm,tar' 125 | ``` 126 | 127 | ## Bali build file format 128 | 129 | Project file `bali.toml`: 130 | 131 | ```toml 132 | # https://toml.io/en/ 133 | name = "bali" 134 | summary = "Bali - Minimalist Golang build and packaging tool" 135 | description = "Bali - Minimalist Golang build and packaging tool" 136 | package-name = "bali-dev" 137 | version = "3.1.0" 138 | license = "MIT" 139 | prefix = "/usr/local" 140 | crates = [ 141 | "cmd/bali", # crates 142 | "cmd/peassets", 143 | ] 144 | 145 | [[include]] 146 | path = "LICENSE" 147 | destination = "share" 148 | rename = "BALI-COPYRIGHT.txt" 149 | permissions = "0664" 150 | 151 | ``` 152 | 153 | Built-in environment variables: 154 | 155 | + `BUILD_VERSION` is filled by the `version` field of balisrc.json 156 | + `BUILD_TIME` is filled by the build time formatted according to `RFC3339` 157 | + `BUILD_COMMIT` is filled by the commit id of the repository (when it is a git repository) 158 | + `BUILD_GOVERSION` is filled by `go version` output (removed `go version` prefix) 159 | + `BUILD_BRANCH` is filled with the branch name of the repository (when it is a git repository) 160 | 161 | Other environment variables can be used in goflags. 162 | 163 | Program build file `crate.toml`: 164 | 165 | ```toml 166 | name = "bali" 167 | description = "Bali - Minimalist Golang build and packaging tool" 168 | destination = "bin" 169 | version = "3.1.0" 170 | goflags = [ 171 | "-ldflags", 172 | "-X 'main.VERSION=$BUILD_VERSION' -X 'main.BUILD_TIME=$BUILD_TIME' -X 'main.BUILD_BRANCH=$BUILD_BRANCH' -X 'main.BUILD_COMMIT=$BUILD_COMMIT' -X 'main.BUILD_REFNAME=$BUILD_REFNAME' -X 'main.BUILD_GOVERSION=$BUILD_GOVERSION'", 173 | ] 174 | 175 | ``` 176 | 177 | Windows-related manifest files (crate.toml sibling):`winres.toml:` 178 | 179 | ```toml 180 | icon = "res/bali.ico" # data:base64-content 181 | manifest = """data: 182 | 183 | Bali 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | true 200 | 201 | 202 | 203 | """ 204 | 205 | [FixedFileInfo] 206 | FileFlagsMask = "3f" 207 | FileFlags = "00" 208 | FileOS = "40004" 209 | FileType = "01" 210 | FileSubType = "00" 211 | 212 | [FixedFileInfo.FileVersion] 213 | Major = 0 214 | Minor = 0 215 | Patch = 0 216 | Build = 0 217 | 218 | [FixedFileInfo.ProductVersion] 219 | Major = 0 220 | Minor = 0 221 | Patch = 0 222 | Build = 0 223 | 224 | [StringFileInfo] 225 | Comments = "" 226 | CompanyName = "Bali Team" 227 | FileDescription = "Bali - Minimalist Golang build and packaging tool" 228 | FileVersion = "" 229 | InternalName = "bali.exe" 230 | LegalCopyright = "Copyright © 2024. Bali contributors" 231 | LegalTrademarks = "" 232 | OriginalFilename = "bali.exe" 233 | PrivateBuild = "" 234 | ProductName = "Bali" 235 | ProductVersion = "" 236 | SpecialBuild = "" 237 | 238 | [VarFileInfo] 239 | [VarFileInfo.Translation] 240 | LangID = "0409" 241 | CharsetID = "04B0" 242 | 243 | ``` 244 | 245 | 246 | Bali integrates [`goversioninfo`](https://github.com/josephspurrier/goversioninfo). When the target is Windows, it can embed version information (`winres.toml`) into the executable program. 247 | 248 | The benefits of adding a reference program manifest are self-evident. For example, Windows UAC privilege escalation, Windows 10 long path support (ie path support> 260 characters), Windows Vista style controls, TaskDialog, DPI settings, etc. all need to modify the application manifest. 249 | 250 | ## Bootstrap 251 | 252 | Usually after installing and configuring the Golang environment, you can follow the following command to complete Bali's bootstrapping: 253 | 254 | UNIX: 255 | 256 | ```shell 257 | ./script/bootstrap.sh 258 | ``` 259 | 260 | Windows: 261 | 262 | ```ps1 263 | # powershell 264 | pwsh ./script/bootstrap.ps1 265 | # cmd 266 | script/bootstrap.bat 267 | ``` 268 | 269 | 270 | ## Github Actions Use bali 271 | 272 | ``` 273 | go install github.com/balibuild/bali/v3/cmd/bali@latest 274 | ``` 275 | 276 | ## Thanks 277 | 278 | Bali's ability to automatically add version information to PE files is inseparable from the contribution of open source projects. Thank you very much [akavel/rsrc](https://github.com/akavel/rsrc) and [josephspurrier/goversioninfo](https://github.com/josephspurrier/goversioninfo) Developer and maintainer of two projects. 279 | 280 | The Bali Github organization and Bali's own icons come from [www.flaticon.com](https://www.flaticon.com/) The creator is [Smashicons](https://www.flaticon.com/authors/smashicons) . 281 | 282 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Bali - 极简的 Golang 构建打包工具 2 | 3 | [![Master Branch Status](https://github.com/balibuild/bali/workflows/CI/badge.svg)](https://github.com/balibuild/bali/actions) 4 | 5 | Bali 是一个使用 Golang 开发的*极简 Golang 构建打包工具*,[Bali(old)](https://github.com/fcharlie/bali) 6 | 7 | ## 功能 8 | 9 | Bali 有一些功能是我觉得有些用处的: 10 | 11 | + 构建参数支持环境变量推导 12 | + 打包,创建压缩包,支持 `rpm`, `tar`, `zip`, `sh` 等。 13 | + Windows 平台支持嵌入版本信息,图标,和应用程序清单。 14 | 15 | 16 | bali 的命令行帮助信息如下: 17 | 18 | ```txt 19 | Usage: bali [flags] 20 | 21 | Bali - Minimalist Golang build and packaging tool 22 | 23 | Flags: 24 | -h, --help Show context-sensitive help. 25 | -M, --module="." Explicitly specify a module directory 26 | -B, --build="build" Explicitly specify a build directory 27 | -V, --verbose Make the operation more talkative 28 | -v, --version Print version information and quit 29 | 30 | Commands: 31 | build Compile the current module (default) 32 | update Update dependencies as recorded in the go.mod 33 | clean Remove generated artifacts 34 | 35 | Run "bali --help" for more information on a command. 36 | 37 | ``` 38 | 39 | bali 构建命令帮助: 40 | 41 | ```txt 42 | Usage: bali build [flags] 43 | 44 | Compile the current module (default) 45 | 46 | Flags: 47 | -h, --help Show context-sensitive help. 48 | -M, --module="." Explicitly specify a module directory 49 | -B, --build="build" Explicitly specify a build directory 50 | -V, --verbose Make the operation more talkative 51 | -v, --version Print version information and quit 52 | 53 | -T, --target="windows" Target OS for which the code is compiled 54 | -A, --arch="amd64" Target architecture for which the code is compiled 55 | --release=STRING Specifies the rpm package tag version 56 | -D, --destination="dest" Specify the package save destination 57 | --pack=PACK,... Packaged in a specific format. supported: zip, 58 | tar, sh, rpm 59 | --compression=STRING Specifies the compression method 60 | ``` 61 | 62 | rpm 支持的压缩算法: 63 | + gzip 64 | + zstd 65 | + lzma 66 | + xz 67 | 68 | tar 支持的压缩算法: 69 | + none --> pure tar 70 | + gzip --> tar.gz 71 | + zstd --> tar.zst 72 | + xz --> tar.xz 73 | + bzip2 --> tar.bz2 74 | + brotli --> tar.br 75 | 76 | sh 支持的压缩算法: 77 | + none --> pure tar 78 | + gzip --> tar.gz 79 | + zstd --> tar.zst 80 | + xz --> tar.xz 81 | + bzip2 --> tar.bz2 82 | 83 | zip 支持的压缩算法: 84 | + deflate 85 | + zstd 86 | + bzip2 87 | + xz 88 | 89 | 90 | ## 使用方法 91 | 92 | 普通构建: 93 | 94 | ```shell 95 | cd /path/to/project 96 | bali 97 | ``` 98 | 99 | 创建 `Tar.gz` 压缩包: 100 | 101 | ```shell 102 | bali --pack=tar 103 | ``` 104 | 105 | 创建 `STGZ` 安装包,主要用于 Linux/macOS 平台: 106 | 107 | ```shell 108 | bali --pack=sh --target=linux --arch=amd64 109 | ``` 110 | 111 | 将安装包输出到指定目录: 112 | 113 | ```shell 114 | bali --pack=rpm --target=linux --arch=amd64 --dest=/tmp/output 115 | ``` 116 | 117 | 一次性创建多种包: 118 | 119 | ```shell 120 | bali --target=linux --arch=arm64 '--pack=sh,rpm,tar' 121 | ``` 122 | 123 | ## Bali 构建文件格式 124 | 125 | 项目文件 `bali.toml`: 126 | 127 | ```toml 128 | # https://toml.io/en/ 129 | name = "bali" 130 | summary = "Bali - Minimalist Golang build and packaging tool" 131 | description = "Bali - Minimalist Golang build and packaging tool" 132 | package-name = "bali-dev" 133 | version = "3.1.0" 134 | license = "MIT" 135 | prefix = "/usr/local" 136 | crates = [ 137 | "cmd/bali", # crates 138 | "cmd/peassets", 139 | ] 140 | 141 | [[include]] 142 | path = "LICENSE" 143 | destination = "share" 144 | rename = "BALI-COPYRIGHT.txt" 145 | permissions = "0664" 146 | 147 | ``` 148 | 149 | 程序构建文件 `crate.toml`: 150 | 151 | ```toml 152 | name = "bali" 153 | description = "Bali - Minimalist Golang build and packaging tool" 154 | destination = "bin" 155 | version = "3.1.0" 156 | goflags = [ 157 | "-ldflags", 158 | "-X 'main.VERSION=$BUILD_VERSION' -X 'main.BUILD_TIME=$BUILD_TIME' -X 'main.BUILD_BRANCH=$BUILD_BRANCH' -X 'main.BUILD_COMMIT=$BUILD_COMMIT' -X 'main.BUILD_REFNAME=$BUILD_REFNAME' -X 'main.BUILD_GOVERSION=$BUILD_GOVERSION'", 159 | ] 160 | 161 | 162 | ``` 163 | 164 | 内置环境变量: 165 | 166 | + `BUILD_VERSION` 由 balisrc.toml 的 `version` 字段填充 167 | + `BUILD_TIME` 由构建时间按照 `RFC3339` 格式化后填充 168 | + `BUILD_COMMIT` 由存储库(为 git 存储库时) 的 commit id 填充 169 | + `BUILD_GOVERSION` 由 `go version` 输出(删除了 `go version` 前缀)填充 170 | + `BUILD_BRANCH` 由存储库(为 git 存储库时) 的分支名填充 171 | 172 | 可以在 goflags 中使用其他环境变量。 173 | 174 | Windows 相关清单文件(crate.toml 同级):`winres.toml:` 175 | 176 | ```toml 177 | icon = "res/bali.ico" # data:base64-content 178 | manifest = """data: 179 | 180 | Bali 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | true 197 | 198 | 199 | 200 | """ 201 | 202 | [FixedFileInfo] 203 | FileFlagsMask = "3f" 204 | FileFlags = "00" 205 | FileOS = "40004" 206 | FileType = "01" 207 | FileSubType = "00" 208 | 209 | [FixedFileInfo.FileVersion] 210 | Major = 0 211 | Minor = 0 212 | Patch = 0 213 | Build = 0 214 | 215 | [FixedFileInfo.ProductVersion] 216 | Major = 0 217 | Minor = 0 218 | Patch = 0 219 | Build = 0 220 | 221 | [StringFileInfo] 222 | Comments = "" 223 | CompanyName = "Bali Team" 224 | FileDescription = "Bali - Minimalist Golang build and packaging tool" 225 | FileVersion = "" 226 | InternalName = "bali.exe" 227 | LegalCopyright = "Copyright © 2024. Bali contributors" 228 | LegalTrademarks = "" 229 | OriginalFilename = "bali.exe" 230 | PrivateBuild = "" 231 | ProductName = "Bali" 232 | ProductVersion = "" 233 | SpecialBuild = "" 234 | 235 | [VarFileInfo] 236 | [VarFileInfo.Translation] 237 | LangID = "0409" 238 | CharsetID = "04B0" 239 | 240 | ``` 241 | 242 | Bali 整合了 [`goversioninfo`](https://github.com/josephspurrier/goversioninfo),在目标为 Windows 时,能够将版本信息 **winres.toml** 嵌入到可执行程序中。 243 | 244 | 添加引用程序清单的好处不言而喻,比如 Windows 的 UAC 提权,Windows 10 长路经支持(即路径支持 >260 字符),Windows Vista 风格控件,TaskDialog,DPI 设置等都需要修改应用程序清单。 245 | 246 | ## 自举 247 | 248 | 通常在安装配置好 Golang 环境后,你可以按照下面的命令完成 Bali 的自举: 249 | 250 | UNIX: 251 | 252 | ```shell 253 | ./script/bootstrap.sh 254 | ``` 255 | 256 | Windows: 257 | 258 | ```ps1 259 | # 使用 powershell 运行 260 | pwsh ./script/bootstrap.ps1 261 | # 或者在 cmd 中运行 262 | script/bootstrap.bat 263 | ``` 264 | 265 | ## 感谢 266 | 267 | Bali 自动添加版本信息到 PE 文件的功能离不开开源项目的贡献,在这里非常感谢 [akavel/rsrc](https://github.com/akavel/rsrc) 和 [josephspurrier/goversioninfo](https://github.com/josephspurrier/goversioninfo) 两个项目的开发者和维护者。 268 | 269 | Bali Github 组织和 Bali 自身的图标来源于 [www.flaticon.com](https://www.flaticon.com/) 制作者为 [Smashicons](https://www.flaticon.com/authors/smashicons)。 270 | 271 | -------------------------------------------------------------------------------- /bali.toml: -------------------------------------------------------------------------------- 1 | # https://toml.io/en/ 2 | name = "bali" 3 | summary = "Bali - Minimalist Golang build and packaging tool" 4 | description = "Bali - Minimalist Golang build and packaging tool" 5 | package-name = "bali-dev" 6 | version = "3.1.0" 7 | license = "MIT" 8 | prefix = "/usr/local" 9 | crates = [ 10 | "cmd/bali", # crates 11 | ] 12 | 13 | [[include]] 14 | path = "LICENSE" 15 | destination = "share" 16 | rename = "BALI-COPYRIGHT.txt" 17 | permissions = "0664" 18 | -------------------------------------------------------------------------------- /cmd/README.md: -------------------------------------------------------------------------------- 1 | # cmd tools -------------------------------------------------------------------------------- /cmd/bali/command_build.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/balibuild/bali/v3/pkg/barrow" 8 | ) 9 | 10 | type BuildCommand struct { 11 | Target string `name:"target" short:"T" help:"Target OS for which the code is compiled" default:"${target}"` // windows/darwin 12 | Arch string `name:"arch" short:"A" help:"Target architecture for which the code is compiled" default:"${arch}"` // amd64/arm64 ... 13 | Release string `name:"release" help:"Specifies the rpm package tag version"` // --release $TASK_ID 14 | Destination string `name:"destination" short:"D" help:"Specify the package save destination" default:"out"` 15 | Pack []string `name:"pack" help:"Packaged in a specific format. supported: zip, tar, sh, rpm, deb, apk, arch"` 16 | Compression string `name:"compression" help:"Specifies the compression method"` 17 | } 18 | 19 | func (c *BuildCommand) Run(g *Globals) error { 20 | b := barrow.BarrowCtx{ 21 | CWD: g.M, 22 | Out: g.B, 23 | Target: c.Target, 24 | Arch: c.Arch, 25 | Release: c.Release, 26 | Destination: c.Destination, 27 | Pack: c.Pack, 28 | Compression: strings.ToLower(c.Compression), 29 | Verbose: g.Verbose, 30 | } 31 | if err := b.Initialize(context.Background()); err != nil { 32 | return err 33 | } 34 | return b.Run(context.Background()) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/bali/command_clean.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/balibuild/bali/v3/pkg/barrow" 5 | ) 6 | 7 | type CleanCommand struct { 8 | Force bool `name:"force" help:"Clean up all the builds"` 9 | Destination string `name:"destination" short:"D" help:"Specify the package save destination" default:"out"` 10 | } 11 | 12 | func (c *CleanCommand) Run(g *Globals) error { 13 | b := barrow.BarrowCtx{ 14 | CWD: g.M, 15 | Out: g.B, 16 | Destination: c.Destination, 17 | Verbose: g.Verbose, 18 | } 19 | return b.Cleanup(c.Force) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/bali/command_update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type UpdateCommand struct { 4 | ALL bool `name:"all" help:"Update all dependencies"` 5 | Modules []string `name:"modules" short:"m" help:"Update modules to the specified version"` 6 | } 7 | 8 | func (c *UpdateCommand) Run(g *Globals) error { 9 | 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /cmd/bali/crate.toml: -------------------------------------------------------------------------------- 1 | name = "bali" 2 | description = "Bali - Minimalist Golang build and packaging tool" 3 | destination = "bin" 4 | version = "3.1.0" 5 | goflags = [ 6 | "-ldflags", 7 | "-X 'main.VERSION=$BUILD_VERSION' -X 'main.BUILD_TIME=$BUILD_TIME' -X 'main.BUILD_BRANCH=$BUILD_BRANCH' -X 'main.BUILD_COMMIT=$BUILD_COMMIT' -X 'main.BUILD_REFNAME=$BUILD_REFNAME' -X 'main.BUILD_GOVERSION=$BUILD_GOVERSION'", 8 | ] 9 | # alias = [ 10 | # "bin/bali-${BUILD_VERSION}-${BUILD_TARGET}-${BUILD_ARCH}", 11 | # ] 12 | -------------------------------------------------------------------------------- /cmd/bali/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "strings" 8 | "unicode" 9 | 10 | "github.com/alecthomas/kong" 11 | ) 12 | 13 | // version info 14 | var ( 15 | VERSION = "3.1.0" 16 | BUILD_TIME string = "NONE" 17 | BUILD_COMMIT string = "NONE" 18 | BUILD_BRANCH string = "NONE" 19 | BUILD_REFNAME string = "NONE" 20 | BUILD_GOVERSION string 21 | ) 22 | 23 | func init() { 24 | if len(BUILD_GOVERSION) == 0 { 25 | BUILD_GOVERSION = fmt.Sprintf("%s %s/%s", strings.Replace(runtime.Version(), "go", "", 1), runtime.GOOS, runtime.GOARCH) 26 | } 27 | } 28 | 29 | func version() { 30 | const template = `Bali - Minimalist Golang build and packaging tool 31 | Version: %s 32 | Branch: %s 33 | Commit: %s 34 | Build Time: %s 35 | Go Version: %s 36 | 37 | ` 38 | const tagTemplate = `Bali - Minimalist Golang build and packaging tool 39 | Version: %s 40 | Release: %s 41 | Commit: %s 42 | Build Time: %s 43 | Go Version: %s 44 | 45 | ` 46 | if len(BUILD_BRANCH) != 0 { 47 | fmt.Fprintf(os.Stdout, template, VERSION, BUILD_BRANCH, BUILD_COMMIT, BUILD_TIME, BUILD_GOVERSION) 48 | return 49 | } 50 | fmt.Fprintf(os.Stdout, tagTemplate, VERSION, strings.TrimPrefix(BUILD_REFNAME, "refs/tags/"), BUILD_COMMIT, BUILD_TIME, BUILD_GOVERSION) 51 | } 52 | 53 | type VersionFlag bool 54 | 55 | func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil } 56 | func (v VersionFlag) IsBool() bool { return true } 57 | func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { 58 | version() 59 | app.Exit(0) 60 | return nil 61 | } 62 | 63 | type Globals struct { 64 | M string `name:"module" short:"M" help:"Explicitly specify a module directory" default:"." type:"path"` 65 | B string `name:"build" short:"B" help:"Explicitly specify a build directory" default:"build" type:"path"` 66 | Verbose bool `name:"verbose" short:"V" help:"Make the operation more talkative"` 67 | Version VersionFlag `name:"version" short:"v" help:"Print version information and quit"` 68 | } 69 | 70 | func (g *Globals) DbgPrint(format string, a ...any) { 71 | if !g.Verbose { 72 | return 73 | } 74 | message := fmt.Sprintf(format, a...) 75 | message = strings.TrimRightFunc(message, unicode.IsSpace) 76 | lines := strings.Split(message, "\n") 77 | for _, line := range lines { 78 | fmt.Fprintf(os.Stderr, "\x1b[33m* %s\x1b[0m\n", line) 79 | } 80 | } 81 | 82 | type App struct { 83 | Globals 84 | Build BuildCommand `cmd:"build" help:"Compile the current module (default)" default:"withargs"` 85 | Update UpdateCommand `cmd:"update" help:"Update dependencies as recorded in the go.mod"` 86 | Clean CleanCommand `cmd:"clean" help:"Remove generated artifacts"` 87 | } 88 | 89 | func main() { 90 | app := App{} 91 | 92 | ctx := kong.Parse(&app, 93 | kong.Name("bali"), 94 | kong.Description("Bali - Minimalist Golang build and packaging tool"), 95 | kong.UsageOnError(), 96 | kong.ConfigureHelp(kong.HelpOptions{ 97 | Compact: true, 98 | }), 99 | kong.Vars{ 100 | "target": runtime.GOOS, 101 | "arch": runtime.GOARCH, 102 | }) 103 | err := ctx.Run(&app.Globals) 104 | if err != nil { 105 | os.Exit(1) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /cmd/bali/res/bali.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/cmd/bali/res/bali.ico -------------------------------------------------------------------------------- /cmd/bali/res/chili.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/cmd/bali/res/chili.png -------------------------------------------------------------------------------- /cmd/bali/res/chili.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /cmd/bali/windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | // bali init on windows 12 | 13 | // const 14 | const ( 15 | EnableVirtualTerminalProcessingMode = 0x4 16 | ) 17 | 18 | var ( 19 | kernel32 = syscall.NewLazyDLL("kernel32.dll") 20 | procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 21 | procSetConsoleMode = kernel32.NewProc("SetConsoleMode") 22 | ) 23 | 24 | func init() { 25 | var mode uint32 26 | // becasue we print message to stderr 27 | h := os.Stderr.Fd() 28 | if r, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 { 29 | _, _, _ = procSetConsoleMode.Call(h, uintptr(mode|EnableVirtualTerminalProcessingMode)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cmd/bali/winres.toml: -------------------------------------------------------------------------------- 1 | icon = "res/bali.ico" # data:base64-content 2 | manifest = """data: 3 | 4 | Bali 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | """ 25 | 26 | [FixedFileInfo] 27 | FileFlagsMask = "3f" 28 | FileFlags = "00" 29 | FileOS = "40004" 30 | FileType = "01" 31 | FileSubType = "00" 32 | 33 | [FixedFileInfo.FileVersion] 34 | Major = 0 35 | Minor = 0 36 | Patch = 0 37 | Build = 0 38 | 39 | [FixedFileInfo.ProductVersion] 40 | Major = 0 41 | Minor = 0 42 | Patch = 0 43 | Build = 0 44 | 45 | [StringFileInfo] 46 | Comments = "" 47 | CompanyName = "Bali Team" 48 | FileDescription = "Bali - Minimalist Golang build and packaging tool" 49 | FileVersion = "" 50 | InternalName = "bali.exe" 51 | LegalCopyright = "Copyright © 2024. Bali contributors" 52 | LegalTrademarks = "" 53 | OriginalFilename = "bali.exe" 54 | PrivateBuild = "" 55 | ProductName = "Bali" 56 | ProductVersion = "" 57 | SpecialBuild = "" 58 | 59 | [VarFileInfo] 60 | [VarFileInfo.Translation] 61 | LangID = "0409" 62 | CharsetID = "04B0" 63 | -------------------------------------------------------------------------------- /docs/bali.docker.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/docs/bali.docker.json -------------------------------------------------------------------------------- /docs/spec.md: -------------------------------------------------------------------------------- 1 | # Bali Specification 2 | 3 | |Attributes|Description| 4 | |---|---| 5 | |Status|Draft| 6 | |Author|Force Charlie| 7 | |Date|2020-04-27| 8 | 9 | ## Overview 10 | 11 | Bali is a minimalist build and packaging tool for Golang projects based on Golang. Bali chose JSON as the file format. The advantage of using JSON is that Golang has built-in support for parsing, and it can be formatted using an editor. There are two types of Bali build files. One is the project file `bali.json`, which is usually in the root directory of the project. It can also be used to create this file in other directories. When running the build, use `bali -w` or `bali /path/to/buildroot` specifies the directory where `bali.json` is located, you can also run `bali` in that directory; another build file is the `balisrc.json` file under the specific program source code directory, `balisrc.json` There should be a `main` package in the directory where bali resolves `balisrc.json` by parsing `dirs` of `bali.json`, similar to the `add_subdirectory` instruction of `cmake`. 12 | 13 | |FileName|Location|Description| 14 | |---|---|---| 15 | |`bali.json`|Usually in the root directory of the project.|| 16 | |`balisrc.json`|Program source code directory|Parse its path through `bali.json`| 17 | 18 | ## bali.json Description 19 | 20 | |Field|Type|Description| 21 | |---|---|---| 22 | |name|string|Project Name| 23 | |version|string|Project Version| 24 | |files|object|install files| 25 | |dirs|string array|build executables| 26 | 27 | File object: 28 | 29 | |Field|Type|Description| 30 | |---|---|---| 31 | |path|string|file relative path| 32 | |destination|string|install destination| 33 | |newname|optional string|rename when install| 34 | |norename|bool|no rename when package| 35 | 36 | ## balisrc.json Description 37 | 38 | |Field|Type|Description| 39 | |---|---|---| 40 | |name|string|Executable Name| 41 | |description|string|Executable description| 42 | |destination|string|Executable install destination| 43 | |version|string|Executable version| 44 | |links|optional string array|Executable symlink when install and create package| 45 | |goflags|optional string array|golang build flags| 46 | |versioninfo|optional string|Executable versioninfo file| 47 | |icon|optional string|EXE icon path| 48 | |manifest|optional string|EXE manifest path| 49 | 50 | 51 | `versioninfo.json` example: 52 | 53 | ```json 54 | { 55 | "FixedFileInfo": { 56 | "FileVersion": { 57 | "Major": 0, 58 | "Minor": 0, 59 | "Patch": 0, 60 | "Build": 0 61 | }, 62 | "ProductVersion": { 63 | "Major": 0, 64 | "Minor": 0, 65 | "Patch": 0, 66 | "Build": 0 67 | }, 68 | "FileFlagsMask": "3f", 69 | "FileFlags": "00", 70 | "FileOS": "40004", 71 | "FileType": "01", 72 | "FileSubType": "00" 73 | }, 74 | "StringFileInfo": { 75 | "Comments": "", 76 | "CompanyName": "Bali Team", 77 | "FileDescription": "Bali - Minimalist Golang build and packaging tool", 78 | "FileVersion": "", 79 | "InternalName": "bali.exe", 80 | "LegalCopyright": "Copyright \u00A9 2024. Bali contributors", 81 | "LegalTrademarks": "", 82 | "OriginalFilename": "bali.exe", 83 | "PrivateBuild": "", 84 | "ProductName": "Bali", 85 | "ProductVersion": "1.0", 86 | "SpecialBuild": "" 87 | }, 88 | "VarFileInfo": { 89 | "Translation": { 90 | "LangID": "0409", 91 | "CharsetID": "04B0" 92 | } 93 | } 94 | } 95 | ``` -------------------------------------------------------------------------------- /docs/unix.md: -------------------------------------------------------------------------------- 1 | # Bali for Unix 2 | 3 | + STGZ -------------------------------------------------------------------------------- /docs/windows.md: -------------------------------------------------------------------------------- 1 | # Bali for Windows 2 | 3 | ## Windows Enhanced Build 4 | 5 | + Embedded Windows program manifest file 6 | + Executable file version information 7 | + Executable file icon -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/balibuild/bali/v3 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/alecthomas/kong v1.11.0 7 | github.com/andybalholm/brotli v1.1.1 8 | github.com/dsnet/compress v0.0.1 9 | github.com/google/rpmpack v0.7.0 10 | github.com/goreleaser/nfpm/v2 v2.42.1 11 | github.com/klauspost/compress v1.18.0 12 | github.com/pelletier/go-toml/v2 v2.2.4 13 | github.com/ulikunitz/xz v0.5.12 14 | ) 15 | 16 | require ( 17 | dario.cat/mergo v1.0.2 // indirect 18 | github.com/AlekSi/pointer v1.2.0 // indirect 19 | github.com/Masterminds/goutils v1.1.1 // indirect 20 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 21 | github.com/Masterminds/sprig/v3 v3.3.0 // indirect 22 | github.com/Microsoft/go-winio v0.6.2 // indirect 23 | github.com/ProtonMail/go-crypto v1.2.0 // indirect 24 | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect 25 | github.com/cavaliergopher/cpio v1.0.1 // indirect 26 | github.com/cloudflare/circl v1.6.0 // indirect 27 | github.com/cyphar/filepath-securejoin v0.4.1 // indirect 28 | github.com/emirpasic/gods v1.18.1 // indirect 29 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 30 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 31 | github.com/go-git/go-git/v5 v5.14.0 // indirect 32 | github.com/gobwas/glob v0.2.3 // indirect 33 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 34 | github.com/google/uuid v1.6.0 // indirect 35 | github.com/goreleaser/chglog v0.7.0 // indirect 36 | github.com/goreleaser/fileglob v1.3.0 // indirect 37 | github.com/huandu/xstrings v1.5.0 // indirect 38 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 39 | github.com/kevinburke/ssh_config v1.2.0 // indirect 40 | github.com/klauspost/pgzip v1.2.6 // indirect 41 | github.com/mitchellh/copystructure v1.2.0 // indirect 42 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 43 | github.com/pjbgf/sha1cd v0.3.2 // indirect 44 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 45 | github.com/shopspring/decimal v1.4.0 // indirect 46 | github.com/skeema/knownhosts v1.3.1 // indirect 47 | github.com/spf13/cast v1.7.1 // indirect 48 | github.com/xanzy/ssh-agent v0.3.3 // indirect 49 | gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect 50 | golang.org/x/crypto v0.36.0 // indirect 51 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect 52 | golang.org/x/net v0.38.0 // indirect 53 | golang.org/x/sys v0.31.0 // indirect 54 | gopkg.in/warnings.v0 v0.1.2 // indirect 55 | gopkg.in/yaml.v3 v3.0.1 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 | github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= 4 | github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= 5 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 6 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 7 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 8 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 9 | github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= 10 | github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= 11 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 12 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 13 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 14 | github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= 15 | github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 16 | github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= 17 | github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= 18 | github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= 19 | github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= 20 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 21 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 22 | github.com/alecthomas/kong v1.11.0 h1:y++1gI7jf8O7G7l4LZo5ASFhrhJvzc+WgF/arranEmM= 23 | github.com/alecthomas/kong v1.11.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= 24 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 25 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 26 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 27 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 28 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 29 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 30 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 31 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 32 | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= 33 | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= 34 | github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8= 35 | github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= 36 | github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= 37 | github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= 38 | github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= 39 | github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 40 | github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 41 | github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 43 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 44 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 45 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 46 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 47 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 48 | github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 49 | github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 50 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 51 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 52 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 53 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 54 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 55 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 56 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 57 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 58 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 59 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 60 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 61 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 62 | github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= 63 | github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= 64 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 65 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 66 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 67 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 68 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 69 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 70 | github.com/google/rpmpack v0.7.0 h1:mA2Yd3/dOmao1ype0DJA8DFquEpslaleywOuglVCrUs= 71 | github.com/google/rpmpack v0.7.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= 72 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 73 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= 75 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 76 | github.com/goreleaser/chglog v0.7.0 h1:/KzXWAeg4DrEz4r3OI6K2Yb8RAsVGeInCUfLWFXL9C0= 77 | github.com/goreleaser/chglog v0.7.0/go.mod h1:2h/yyq9xvTUeM9tOoucBP+jri8Dj28splx+SjlYkklc= 78 | github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I= 79 | github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU= 80 | github.com/goreleaser/nfpm/v2 v2.42.1 h1:xu2pLRgQuz2ab+YZFoeIzwU/M5jjjCKDGwv1lRbVGvk= 81 | github.com/goreleaser/nfpm/v2 v2.42.1/go.mod h1:dY53KWYKebkOocxgkmpM7SRX0Nv5hU+jEu2kIaM4/LI= 82 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 83 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 84 | github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= 85 | github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 86 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 87 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 88 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 89 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 90 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 91 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 92 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 93 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 94 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 95 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 96 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 97 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 98 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 99 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 100 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 101 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 102 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 103 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 104 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 105 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= 106 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 107 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 108 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 109 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 110 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 111 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 112 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 113 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 114 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 115 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 116 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 117 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 118 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 119 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 120 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 121 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 122 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 123 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 124 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 125 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 126 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 127 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 128 | github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= 129 | github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 130 | github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= 131 | github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= 132 | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= 133 | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= 134 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 135 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 136 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 137 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 138 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 139 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 140 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 141 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 142 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 143 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 144 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 145 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 146 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 147 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 148 | gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= 149 | gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= 150 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 151 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 152 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 153 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 154 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 155 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 156 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 157 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 158 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 163 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 164 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 165 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 166 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 167 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 168 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 169 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 170 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 171 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 172 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 173 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 174 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 175 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 176 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 177 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 178 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 179 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 180 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 181 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 182 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 183 | -------------------------------------------------------------------------------- /makeico/ico.go: -------------------------------------------------------------------------------- 1 | package makeico 2 | 3 | // origin https://github.com/Kodeworks/golang-image-ico 4 | // https://github.com/shibukawa/golang-image-ico 5 | 6 | import ( 7 | "bytes" 8 | "encoding/binary" 9 | "image" 10 | "image/draw" 11 | "image/png" 12 | "io" 13 | ) 14 | 15 | type icondir struct { 16 | reserved uint16 17 | imageType uint16 18 | numImages uint16 19 | } 20 | 21 | type icondirentry struct { 22 | imageWidth uint8 23 | imageHeight uint8 24 | numColors uint8 25 | reserved uint8 26 | colorPlanes uint16 27 | bitsPerPixel uint16 28 | sizeInBytes uint32 29 | offset uint32 30 | } 31 | 32 | func newIcondirentry(offset int) icondirentry { 33 | var ide icondirentry 34 | ide.colorPlanes = 1 // windows is supposed to not mind 0 or 1, but other icon files seem to have 1 here 35 | ide.bitsPerPixel = 32 // can be 24 for bitmap or 24/32 for png. Set to 32 for now 36 | ide.offset = uint32(offset) //6 icondir + 16 icondirentry, next image will be this image size + 16 icondirentry, etc 37 | return ide 38 | } 39 | 40 | // EncodePNG encoding 41 | func EncodePNG(w io.Writer, images ...image.Image) (err error) { 42 | id := icondir{ 43 | imageType: 1, 44 | numImages: uint16(len(images)), 45 | } 46 | err = binary.Write(w, binary.LittleEndian, id) 47 | if err != nil { 48 | return 49 | } 50 | imageSizes := make([]int, len(images)) 51 | 52 | pngbb := new(bytes.Buffer) 53 | for i, im := range images { 54 | prevSize := len(pngbb.Bytes()) 55 | b := im.Bounds() 56 | m := image.NewRGBA(b) 57 | draw.Draw(m, b, im, b.Min, draw.Src) 58 | err = png.Encode(pngbb, m) 59 | if err != nil { 60 | return 61 | } 62 | imageSizes[i] = len(pngbb.Bytes()) - prevSize 63 | } 64 | offset := 6 + 16*len(images) 65 | for i, im := range images { 66 | bounds := im.Bounds() 67 | entry := icondirentry{ 68 | imageWidth: uint8(bounds.Dx()), 69 | imageHeight: uint8(bounds.Dy()), 70 | colorPlanes: 1, 71 | bitsPerPixel: 32, 72 | sizeInBytes: uint32(imageSizes[i]), 73 | offset: uint32(offset), 74 | } 75 | offset += imageSizes[i] 76 | err = binary.Write(w, binary.LittleEndian, entry) 77 | if err != nil { 78 | return 79 | } 80 | } 81 | _, err = w.Write(pngbb.Bytes()) 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /module/goversioninfo/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joseph Spurrier 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 | -------------------------------------------------------------------------------- /module/goversioninfo/README.md: -------------------------------------------------------------------------------- 1 | GoVersionInfo 2 | ========== 3 | [![Build Status](https://travis-ci.org/josephspurrier/goversioninfo.svg)](https://travis-ci.org/josephspurrier/goversioninfo) [![Coverage Status](https://coveralls.io/repos/josephspurrier/goversioninfo/badge.svg)](https://coveralls.io/r/josephspurrier/goversioninfo) [![GoDoc](https://godoc.org/github.com/josephspurrier/goversioninfo?status.svg)](https://godoc.org/github.com/josephspurrier/goversioninfo) 4 | 5 | Microsoft Windows File Properties/Version Info and Icon Resource Generator for the Go Language 6 | 7 | Package creates a syso file which contains Microsoft Windows Version Information and an optional icon. When you run "go build", Go will embed the version information and an optional icon and an optional manifest in the executable. Go will automatically use the syso file if it's in the same directory as the main() function. 8 | 9 | Example of the file properties you can set using this package: 10 | 11 | ![Image of File Properties](https://cloud.githubusercontent.com/assets/2394539/12073634/0b32cb04-b0f6-11e5-9d8e-f9923ca554cf.jpg) 12 | 13 | ## Usage 14 | 15 | To install, run the following command: 16 | ~~~ 17 | go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo 18 | ~~~ 19 | 20 | Copy testdata/resource/versioninfo.json into your working directory and then modify the file with your own settings. 21 | 22 | Add a similar text to the top of your Go source code (-icon and -manifest are optional, but can also be specified in the versioninfo.json file): 23 | ~~~ go 24 | //go:generate goversioninfo -icon=testdata/resource/icon.ico -manifest=testdata/resource/goversioninfo.exe.manifest 25 | ~~~ 26 | 27 | Run the Go commands in this order so goversioninfo will create a file called resource.syso in the same directory as the Go source code. 28 | ~~~ 29 | go generate 30 | go build 31 | ~~~ 32 | 33 | ## Command-Line Flags 34 | 35 | Complete list of the flags for goversioninfo: 36 | 37 | ~~~ 38 | -charset=0: charset ID 39 | -comment="": StringFileInfo.Comments 40 | -company="": StringFileInfo.CompanyName 41 | -copyright="": StringFileInfo.LegalCopyright 42 | -description="": StringFileInfo.FileDescription 43 | -example=false: just dump out an example versioninfo.json to stdout 44 | -file-version="": StringFileInfo.FileVersion 45 | -icon="": icon file name 46 | -internal-name="": StringFileInfo.InternalName 47 | -manifest="": manifest file name 48 | -o="resource.syso": output file name 49 | -platform-specific=false: output i386 and amd64 named resource.syso, ignores -o 50 | -original-name="": StringFileInfo.OriginalFilename 51 | -private-build="": StringFileInfo.PrivateBuild 52 | -product-name="": StringFileInfo.ProductName 53 | -product-version="": StringFileInfo.ProductVersion 54 | -special-build="": StringFileInfo.SpecialBuild 55 | -trademark="": StringFileInfo.LegalTrademarks 56 | -translation=0: translation ID 57 | -64:false: generate 64-bit binaries on true 58 | -ver-major=-1: FileVersion.Major 59 | -ver-minor=-1: FileVersion.Minor 60 | -ver-patch=-1: FileVersion.Patch 61 | -ver-build=-1: FileVersion.Build 62 | -product-ver-major=-1: ProductVersion.Major 63 | -product-ver-minor=-1: ProductVersion.Minor 64 | -product-ver-patch=-1: ProductVersion.Patch 65 | -product-ver-build=-1: ProductVersion.Build 66 | ~~~ 67 | 68 | You can look over the Microsoft Resource Information: [VERSIONINFO resource](https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx) 69 | 70 | You can look through the Microsoft Version Information structures: [Version Information Structures](https://msdn.microsoft.com/en-us/library/windows/desktop/ff468916(v=vs.85).aspx) 71 | 72 | ## PowerShell Differences 73 | 74 | In PowerShell, the version components are named differently than the fields in 75 | the versioninfo.json file: 76 | 77 | ``` 78 | PowerShell: versioninfo.json: 79 | ----------- ----------------- 80 | FileMajorPart = FileVersion.Major 81 | FileMinorPart = FileVersion.Minor 82 | FileBuildPart = FileVersion.Patch 83 | FilePrivatePart = FileVersion.Build 84 | ProductMajorPart = ProductVersion.Major 85 | ProductMinorPart = ProductVersion.Minor 86 | ProductBuildPart = ProductVersion.Patch 87 | ProductPrivatePart = ProductVersion.Build 88 | 89 | ``` 90 | 91 | If you find any other differences, let me know. 92 | 93 | ## Alternatives to this Tool 94 | 95 | You can also use [windres](https://sourceware.org/binutils/docs/binutils/windres.html) to create the syso file. The windres executable is available in either [MinGW](http://www.mingw.org/) or [tdm-gcc](http://tdm-gcc.tdragon.net/). 96 | 97 | Below is a sample batch file you can use to create a .syso file from a .rc file. There are sample .rc files in the testdata/rc folder. 98 | 99 | ~~~ 100 | @ECHO OFF 101 | 102 | SET PATH=C:\TDM-GCC-64\bin;%PATH% 103 | REM SET PATH=C:\mingw64\bin;%PATH% 104 | 105 | windres -i testdata/rc/versioninfo.rc -O coff -o versioninfo.syso 106 | 107 | PAUSE 108 | ~~~ 109 | 110 | The information on how to create a .rc file is available [here](https://msdn.microsoft.com/en-us/library/windows/desktop/aa381043(v=vs.85).aspx). You can use the testdata/rc/versioninfo.rc file to create a .syso file that contains version info, icon, and manifest. 111 | 112 | ## Issues 113 | 114 | The majority of the code for the creation of the syso file is from this package: [https://github.com/akavel/rsrc](https://github.com/akavel/rsrc) 115 | 116 | There is an [issue](https://github.com/akavel/rsrc/issues/12) with adding the icon resource that prevents your application from being compressed or modified with a resource editor. Please use with caution. 117 | 118 | ## Major Contributions 119 | 120 | Thanks to [Tamás Gulácsi](https://github.com/tgulacsi) for his superb code additions, refactoring, and optimization to make this a solid package. 121 | 122 | Thanks to [Mateusz Czaplinski](https://github.com/akavel/rsrc) for his embedded binary resource package with icon and manifest functionality. 123 | -------------------------------------------------------------------------------- /module/goversioninfo/VERSION: -------------------------------------------------------------------------------- 1 | https://github.com/josephspurrier/goversioninfo 2 | 14b0ab84c6ca2aeee22bf6aa3f8139db4a53d46c 3 | -------------------------------------------------------------------------------- /module/goversioninfo/goversioninfo.go: -------------------------------------------------------------------------------- 1 | // Package goversioninfo creates a syso file which contains Microsoft Version Information and an optional icon. 2 | package goversioninfo 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "reflect" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/balibuild/bali/v3/module/rsrc/binutil" 16 | "github.com/balibuild/bali/v3/module/rsrc/coff" 17 | ) 18 | 19 | // VersionInfo data container 20 | type VersionInfo struct { 21 | Icon string `toml:"icon,omitempty" json:"icon,omitempty"` 22 | Manifest string `toml:"manifest,omitempty" json:"manifest,omitempty"` // path or content 23 | FixedFileInfo FixedFileInfo `toml:"FixedFileInfo,omitempty" json:"FixedFileInfo,omitempty"` 24 | StringFileInfo StringFileInfo `toml:"StringFileInfo,omitempty" json:"StringFileInfo,omitempty"` 25 | VarFileInfo VarFileInfo `toml:"VarFileInfo,omitempty" json:"VarFileInfo,omitempty"` 26 | Timestamp bool `toml:"Timestamp,omitempty" json:"Timestamp,omitempty"` 27 | structure VSVersionInfo 28 | buffer bytes.Buffer 29 | } 30 | 31 | // Translation with langid and charsetid. 32 | type Translation struct { 33 | LangID LangID `toml:"LangID,omitempty" json:"LangID,omitempty"` 34 | CharsetID CharsetID `toml:"CharsetID,omitempty" json:"CharsetID,omitempty"` 35 | } 36 | 37 | // FileVersion with 3 parts. 38 | type FileVersion struct { 39 | Major int `toml:"Major,omitempty" json:"Major,omitempty"` 40 | Minor int `toml:"Minor,omitempty" json:"Minor,omitempty"` 41 | Patch int `toml:"Patch,omitempty" json:"Patch,omitempty"` 42 | Build int `toml:"Build,omitempty" json:"Build,omitempty"` 43 | } 44 | 45 | // Overwrite version 46 | func (fv *FileVersion) Overwrite(ver string) error { 47 | if len(ver) == 0 { 48 | return nil 49 | } 50 | vss := strings.Split(ver, ".") 51 | if len(vss) > 3 && fv.Build == 0 { 52 | fv.Build, _ = strconv.Atoi(vss[3]) 53 | } 54 | if len(vss) > 2 && fv.Patch == 0 { 55 | fv.Patch, _ = strconv.Atoi(vss[2]) 56 | } 57 | if len(vss) > 1 && fv.Minor == 0 { 58 | fv.Minor, _ = strconv.Atoi(vss[1]) 59 | } 60 | var err error 61 | fv.Major, err = strconv.Atoi(vss[0]) 62 | return err 63 | } 64 | 65 | // FixedFileInfo contains file characteristics - leave most of them at the defaults. 66 | type FixedFileInfo struct { 67 | FileVersion FileVersion `toml:"FileVersion,omitempty" json:"FileVersion,omitempty"` 68 | ProductVersion FileVersion `toml:"ProductVersion,omitempty" json:"ProductVersion,omitempty"` 69 | FileFlagsMask string `toml:"FileFlagsMask,omitempty" json:"FileFlagsMask,omitempty"` 70 | FileFlags string `toml:"FileFlags,omitempty" json:"FileFlags,omitempty"` 71 | FileOS string `toml:"FileOS,omitempty" json:"FileOS,omitempty"` 72 | FileType string `toml:"FileType,omitempty" json:"FileType,omitempty"` 73 | FileSubType string `toml:"FileSubType,omitempty" json:"FileSubType,omitempty"` 74 | } 75 | 76 | // VarFileInfo is the translation container. 77 | type VarFileInfo struct { 78 | Translation `toml:"Translation,omitempty" json:"Translation,omitempty"` 79 | } 80 | 81 | // StringFileInfo is what you want to change. 82 | type StringFileInfo struct { 83 | Comments string `toml:"Comments,omitempty" json:"Comments,omitempty"` 84 | CompanyName string `toml:"CompanyName,omitempty" json:"CompanyName,omitempty"` 85 | FileDescription string `toml:"FileDescription,omitempty" json:"FileDescription,omitempty"` 86 | FileVersion string `toml:"FileVersion,omitempty" json:"FileVersion,omitempty"` 87 | InternalName string `toml:"InternalName,omitempty" json:"InternalName,omitempty"` 88 | LegalCopyright string `toml:"LegalCopyright,omitempty" json:"LegalCopyright,omitempty"` 89 | LegalTrademarks string `toml:"LegalTrademarks,omitempty" json:"LegalTrademarks,omitempty"` 90 | OriginalFilename string `toml:"OriginalFilename,omitempty" json:"OriginalFilename,omitempty"` 91 | PrivateBuild string `toml:"PrivateBuild,omitempty" json:"PrivateBuild,omitempty"` 92 | ProductName string `toml:"ProductName,omitempty" json:"ProductName,omitempty"` 93 | ProductVersion string `toml:"ProductVersion,omitempty" json:"ProductVersion,omitempty"` 94 | SpecialBuild string `toml:"SpecialBuild,omitempty" json:"SpecialBuild,omitempty"` 95 | } 96 | 97 | // ***************************************************************************** 98 | // Helpers 99 | // ***************************************************************************** 100 | 101 | // SizedReader is a *bytes.Buffer. 102 | type SizedReader struct { 103 | *bytes.Buffer 104 | } 105 | 106 | // Size returns the length of the buffer. 107 | func (s SizedReader) Size() int64 { 108 | return int64(s.Buffer.Len()) 109 | } 110 | 111 | func str2Uint32(s string) uint32 { 112 | if s == "" { 113 | return 0 114 | } 115 | u, err := strconv.ParseUint(s, 16, 32) 116 | if err != nil { 117 | log.Printf("Error parsing %q as uint32: %v", s, err) 118 | return 0 119 | } 120 | 121 | return uint32(u) 122 | } 123 | 124 | func padString(s string, zeros int) []byte { 125 | b := make([]byte, 0, len([]rune(s))*2) 126 | for _, x := range s { 127 | tt := int32(x) 128 | 129 | b = append(b, byte(tt)) 130 | if tt > 255 { 131 | tt = tt >> 8 132 | b = append(b, byte(tt)) 133 | } else { 134 | b = append(b, byte(0)) 135 | } 136 | } 137 | 138 | for i := 0; i < zeros; i++ { 139 | b = append(b, 0x00) 140 | } 141 | 142 | return b 143 | } 144 | 145 | func padBytes(i int) []byte { 146 | return make([]byte, i) 147 | } 148 | 149 | func (f *FileVersion) getVersionHighString() string { 150 | return fmt.Sprintf("%04x%04x", f.Major, f.Minor) 151 | } 152 | 153 | func (f *FileVersion) getVersionLowString() string { 154 | return fmt.Sprintf("%04x%04x", f.Patch, f.Build) 155 | } 156 | 157 | // GetVersionString returns a string representation of the version 158 | func (f *FileVersion) GetVersionString() string { 159 | return fmt.Sprintf("%d.%d.%d.%d", f.Major, f.Minor, f.Patch, f.Build) 160 | } 161 | 162 | func (t Translation) getTranslationString() string { 163 | return fmt.Sprintf("%04X%04X", t.LangID, t.CharsetID) 164 | } 165 | 166 | func (t Translation) getTranslation() string { 167 | return fmt.Sprintf("%04x%04x", t.CharsetID, t.LangID) 168 | } 169 | 170 | // ***************************************************************************** 171 | // IO Methods 172 | // ***************************************************************************** 173 | 174 | // Walk writes the data buffer with hexadecimal data from the structs 175 | func (vi *VersionInfo) Walk() { 176 | // Create a buffer 177 | var b bytes.Buffer 178 | w := binutil.Writer{W: &b} 179 | 180 | // Write to the buffer 181 | binutil.Walk(vi.structure, func(v reflect.Value, path string) error { 182 | if binutil.Plain(v.Kind()) { 183 | w.WriteLE(v.Interface()) 184 | } 185 | return nil 186 | }) 187 | 188 | vi.buffer = b 189 | } 190 | 191 | func (vi *VersionInfo) loadManifest(cwd string, rsrc *coff.Coff, newID chan uint16) error { 192 | if manifest, ok := strings.CutPrefix(vi.Manifest, "data:"); ok { 193 | id := <-newID 194 | rsrc.AddResource(rtManifest, id, strings.NewReader(manifest)) 195 | return nil 196 | } 197 | if len(vi.Manifest) == 0 { 198 | return nil 199 | } 200 | 201 | fd, err := binutil.SizedOpen(filepath.Join(cwd, vi.Manifest)) 202 | if err != nil { 203 | return err 204 | } 205 | defer fd.Close() 206 | 207 | id := <-newID 208 | rsrc.AddResource(rtManifest, id, fd) 209 | return nil 210 | } 211 | 212 | // WriteSyso creates a resource file from the version info and optionally an icon. 213 | // arch must be an architecture string accepted by coff.Arch, like "386" or "amd64" waiting support "arm" and "arm64" 214 | func (vi *VersionInfo) WriteSyso(cwd string, saveTo string, arch string) error { 215 | 216 | // Channel for generating IDs 217 | newID := make(chan uint16) 218 | go func() { 219 | for i := uint16(1); ; i++ { 220 | newID <- i 221 | } 222 | }() 223 | 224 | // Create a new RSRC section 225 | rsrc := coff.NewRSRC() 226 | 227 | // Set the architecture 228 | err := rsrc.Arch(arch) 229 | if err != nil { 230 | return err 231 | } 232 | 233 | // ID 16 is for Version Information 234 | rsrc.AddResource(16, 1, SizedReader{bytes.NewBuffer(vi.buffer.Bytes())}) 235 | if err := vi.loadManifest(cwd, rsrc, newID); err != nil { 236 | return err 237 | } 238 | // If icon is enabled 239 | if vi.Icon != "" { 240 | if err := addIcon(rsrc, filepath.Join(cwd, vi.Icon), newID); err != nil { 241 | return err 242 | } 243 | } 244 | 245 | rsrc.Freeze() 246 | 247 | // Write to file 248 | return writeCoff(rsrc, saveTo) 249 | } 250 | 251 | // WriteHex creates a hex file for debugging version info 252 | func (vi *VersionInfo) WriteHex(saveTo string) error { 253 | return os.WriteFile(saveTo, vi.buffer.Bytes(), 0655) 254 | } 255 | 256 | func writeCoff(coff *coff.Coff, saveTo string) error { 257 | out, err := os.Create(saveTo) 258 | if err != nil { 259 | return err 260 | } 261 | if err = writeCoffTo(out, coff); err != nil { 262 | return fmt.Errorf("error writing %q: %v", saveTo, err) 263 | } 264 | return nil 265 | } 266 | 267 | func writeCoffTo(w io.WriteCloser, coff *coff.Coff) error { 268 | bw := binutil.Writer{W: w} 269 | 270 | // write the resulting file to disk 271 | binutil.Walk(coff, func(v reflect.Value, path string) error { 272 | if binutil.Plain(v.Kind()) { 273 | bw.WriteLE(v.Interface()) 274 | return nil 275 | } 276 | vv, ok := v.Interface().(binutil.SizedReader) 277 | if ok { 278 | bw.WriteFromSized(vv) 279 | return binutil.WALK_SKIP 280 | } 281 | return nil 282 | }) 283 | 284 | err := bw.Err 285 | if closeErr := w.Close(); closeErr != nil && err == nil { 286 | err = closeErr 287 | } 288 | return err 289 | } 290 | -------------------------------------------------------------------------------- /module/goversioninfo/icon.go: -------------------------------------------------------------------------------- 1 | package goversioninfo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "os" 7 | 8 | "github.com/balibuild/bali/v3/module/rsrc/coff" 9 | "github.com/balibuild/bali/v3/module/rsrc/ico" 10 | ) 11 | 12 | // ***************************************************************************** 13 | /* 14 | Code from https://github.com/akavel/rsrc 15 | 16 | The MIT License (MIT) 17 | 18 | Copyright (c) 2013-2014 The rsrc Authors. 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | */ 38 | // ***************************************************************************** 39 | 40 | const ( 41 | rtIcon = coff.RT_ICON 42 | rtGroupIcon = coff.RT_GROUP_ICON 43 | rtManifest = coff.RT_MANIFEST 44 | ) 45 | 46 | // on storing icons, see: http://blogs.msdn.com/b/oldnewthing/archive/2012/07/20/10331787.aspx 47 | type gRPICONDIR struct { 48 | ico.ICONDIR 49 | Entries []gRPICONDIRENTRY 50 | } 51 | 52 | func (group gRPICONDIR) Size() int64 { 53 | return int64(binary.Size(group.ICONDIR) + len(group.Entries)*binary.Size(group.Entries[0])) 54 | } 55 | 56 | type gRPICONDIRENTRY struct { 57 | ico.IconDirEntryCommon 58 | ID uint16 59 | } 60 | 61 | func addIcon(coff *coff.Coff, iconPath string, newID <-chan uint16) error { 62 | fd, err := os.Open(iconPath) 63 | if err != nil { 64 | return err 65 | } 66 | defer fd.Close() 67 | 68 | icons, err := ico.DecodeHeaders(fd) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | if len(icons) > 0 { 74 | // RT_ICONs 75 | group := gRPICONDIR{ICONDIR: ico.ICONDIR{ 76 | Reserved: 0, // magic num. 77 | Type: 1, // magic num. 78 | Count: uint16(len(icons)), 79 | }} 80 | gid := <-newID 81 | for _, icon := range icons { 82 | id := <-newID 83 | buff, err := bufferIcon(fd, int64(icon.ImageOffset), int(icon.BytesInRes)) 84 | if err != nil { 85 | return err 86 | } 87 | coff.AddResource(rtIcon, id, buff) 88 | group.Entries = append(group.Entries, gRPICONDIRENTRY{IconDirEntryCommon: icon.IconDirEntryCommon, ID: id}) 89 | } 90 | coff.AddResource(rtGroupIcon, gid, group) 91 | } 92 | return nil 93 | } 94 | 95 | func bufferIcon(f *os.File, offset int64, size int) (*bytes.Reader, error) { 96 | data := make([]byte, size) 97 | _, err := f.ReadAt(data, offset) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return bytes.NewReader(data), nil 102 | } 103 | -------------------------------------------------------------------------------- /module/goversioninfo/lang_cs.go: -------------------------------------------------------------------------------- 1 | // Contribution by Tamás Gulácsi 2 | 3 | package goversioninfo 4 | 5 | import ( 6 | "encoding/json" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/pelletier/go-toml/v2" 11 | ) 12 | 13 | // CharsetID must use be a character-set identifier from: 14 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx#charsetID 15 | type CharsetID uint16 16 | 17 | // CharsetID constants 18 | const ( 19 | Cs7ASCII = CharsetID(0) // Cs7ASCII: 0 0000 7-bit ASCII 20 | CsJIS = CharsetID(932) // CsJIS: 932 03A4 Japan (Shift ? JIS X-0208) 21 | CsKSC = CharsetID(949) // CsKSC: 949 03B5 Korea (Shift ? KSC 5601) 22 | CsBig5 = CharsetID(950) // CsBig5: 950 03B6 Taiwan (Big5) 23 | CsUnicode = CharsetID(1200) // CsUnicode: 1200 04B0 Unicode 24 | CsLatin2 = CharsetID(1250) // CsLatin2: 1250 04E2 Latin-2 (Eastern European) 25 | CsCyrillic = CharsetID(1251) // CsCyrillic: 1251 04E3 Cyrillic 26 | CsMultilingual = CharsetID(1252) // CsMultilingual: 1252 04E4 Multilingual 27 | CsGreek = CharsetID(1253) // CsGreek: 1253 04E5 Greek 28 | CsTurkish = CharsetID(1254) // CsTurkish: 1254 04E6 Turkish 29 | CsHebrew = CharsetID(1255) // CsHebrew: 1255 04E7 Hebrew 30 | CsArabic = CharsetID(1256) // CsArabic: 1256 04E8 Arabic 31 | ) 32 | 33 | // UnmarshalText converts the string to a CharsetID 34 | func (cs *CharsetID) UnmarshalText(b []byte) error { 35 | if len(b) == 0 { 36 | return nil 37 | } 38 | s := string(b) 39 | switch { 40 | case strings.HasPrefix(s, "\""): 41 | if err := toml.Unmarshal(b, &s); err != nil { 42 | return err 43 | } 44 | default: 45 | s = strings.TrimPrefix(s, "0x") 46 | } 47 | u, err := strconv.ParseUint(s, 16, 16) 48 | if err != nil { 49 | return err 50 | } 51 | *cs = CharsetID(u) 52 | return nil 53 | } 54 | 55 | // UnmarshalJSON converts the string to a CharsetID 56 | func (cs *CharsetID) UnmarshalJSON(p []byte) error { 57 | if len(p) == 0 { 58 | return nil 59 | } 60 | if p[0] != '"' { 61 | var u uint16 62 | if err := json.Unmarshal(p, &u); err != nil { 63 | return err 64 | } 65 | *cs = CharsetID(u) 66 | return nil 67 | } 68 | var s string 69 | if err := json.Unmarshal(p, &s); err != nil { 70 | return err 71 | } 72 | u, err := strconv.ParseUint(s, 16, 16) 73 | if err != nil { 74 | return err 75 | } 76 | *cs = CharsetID(u) 77 | return nil 78 | } 79 | 80 | // LangID must use be a character-set identifier from: 81 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx#langID 82 | type LangID uint16 83 | 84 | // UnmarshalText converts the string to a LangID 85 | func (lng *LangID) UnmarshalText(b []byte) error { 86 | if len(b) == 0 { 87 | return nil 88 | } 89 | s := string(b) 90 | switch { 91 | case strings.HasPrefix(s, "\""): 92 | if err := toml.Unmarshal(b, &s); err != nil { 93 | return err 94 | } 95 | default: 96 | s = strings.TrimPrefix(s, "0x") 97 | } 98 | u, err := strconv.ParseUint(s, 16, 16) 99 | if err != nil { 100 | return err 101 | } 102 | *lng = LangID(u) 103 | return nil 104 | } 105 | 106 | // UnmarshalJSON converts the string to a LangID 107 | func (lng *LangID) UnmarshalJSON(p []byte) error { 108 | if len(p) == 0 { 109 | return nil 110 | } 111 | if p[0] != '"' { 112 | var u uint16 113 | if err := json.Unmarshal(p, &u); err != nil { 114 | return err 115 | } 116 | *lng = LangID(u) 117 | return nil 118 | } 119 | var s string 120 | if err := json.Unmarshal(p, &s); err != nil { 121 | return err 122 | } 123 | u, err := strconv.ParseUint(s, 16, 16) 124 | if err != nil { 125 | return err 126 | } 127 | *lng = LangID(u) 128 | return nil 129 | } 130 | 131 | // LangID constants 132 | const ( 133 | LngArabic = LangID(0x0401) // LngArabic: 0x0401 Arabic 134 | LngBulgarian = LangID(0x0402) // LngBulgarian: 0x0402 Bulgarian 135 | LngCatalan = LangID(0x0403) // LngCatalan: 0x0403 Catalan 136 | LngTraditionalChinese = LangID(0x0404) // LngTraditionalChinese: 0x0404 Traditional Chinese 137 | LngCzech = LangID(0x0405) // LngCzech: 0x0405 Czech 138 | LngDanish = LangID(0x0406) // LngDanish: 0x0406 Danish 139 | LngGerman = LangID(0x0407) // LngGerman: 0x0407 German 140 | LngGreek = LangID(0x0408) // LngGreek: 0x0408 Greek 141 | LngUSEnglish = LangID(0x0409) // LngUSEnglish: 0x0409 U.S. English 142 | LngCastilianSpanish = LangID(0x040A) // LngCastilianSpanish: 0x040A Castilian Spanish 143 | LngFinnish = LangID(0x040B) // LngFinnish: 0x040B Finnish 144 | LngFrench = LangID(0x040C) // LngFrench: 0x040C French 145 | LngHebrew = LangID(0x040D) // LngHebrew: 0x040D Hebrew 146 | LngHungarian = LangID(0x040E) // LngHungarian: 0x040E Hungarian 147 | LngIcelandic = LangID(0x040F) // LngIcelandic: 0x040F Icelandic 148 | LngItalian = LangID(0x0410) // LngItalian: 0x0410 Italian 149 | LngJapanese = LangID(0x0411) // LngJapanese: 0x0411 Japanese 150 | LngKorean = LangID(0x0412) // LngKorean: 0x0412 Korean 151 | LngDutch = LangID(0x0413) // LngDutch: 0x0413 Dutch 152 | LngNorwegianBokmal = LangID(0x0414) // LngNorwegianBokmal: 0x0414 Norwegian ? Bokmal 153 | LngPolish = LangID(0x0415) // LngPolish: 0x0415 Polish 154 | LngPortugueseBrazil = LangID(0x0416) // LngPortugueseBrazil: 0x0416 Portuguese (Brazil) 155 | LngRhaetoRomanic = LangID(0x0417) // LngRhaetoRomanic: 0x0417 Rhaeto-Romanic 156 | LngRomanian = LangID(0x0418) // LngRomanian: 0x0418 Romanian 157 | LngRussian = LangID(0x0419) // LngRussian: 0x0419 Russian 158 | LngCroatoSerbianLatin = LangID(0x041A) // LngCroatoSerbianLatin: 0x041A Croato-Serbian (Latin) 159 | LngSlovak = LangID(0x041B) // LngSlovak: 0x041B Slovak 160 | LngAlbanian = LangID(0x041C) // LngAlbanian: 0x041C Albanian 161 | LngSwedish = LangID(0x041D) // LngSwedish: 0x041D Swedish 162 | LngThai = LangID(0x041E) // LngThai: 0x041E Thai 163 | LngTurkish = LangID(0x041F) // LngTurkish: 0x041F Turkish 164 | LngUrdu = LangID(0x0420) // LngUrdu: 0x0420 Urdu 165 | LngBahasa = LangID(0x0421) // LngBahasa: 0x0421 Bahasa 166 | LngSimplifiedChinese = LangID(0x0804) // LngSimplifiedChinese: 0x0804 Simplified Chinese 167 | LngSwissGerman = LangID(0x0807) // LngSwiss German: 0x0807 Swiss German 168 | LngUKEnglish = LangID(0x0809) // LngUKEnglish: 0x0809 U.K. English 169 | LngSpanishMexico = LangID(0x080A) // LngSpanishMexico: 0x080A Spanish (Mexico) 170 | LngBelgianFrench = LangID(0x080C) // LngBelgian French: 0x080C Belgian French 171 | LngSwissItalian = LangID(0x0810) // LngSwiss Italian: 0x0810 Swiss Italian 172 | LngBelgianDutch = LangID(0x0813) // LngBelgian Dutch: 0x0813 Belgian Dutch 173 | LngNorwegianNynorsk = LangID(0x0814) // LngNorwegianNynorsk: 0x0814 Norwegian ? Nynorsk 174 | LngPortuguesePortugal = LangID(0x0816) // LngPortuguese (Portugal): 0x0816 Portuguese (Portugal) 175 | LngSerboCroatianCyrillic = LangID(0x081A) // LngSerboCroatianCyrillic: 0x081A Serbo-Croatian (Cyrillic) 176 | LngCanadianFrench = LangID(0x0C0C) // LngCanadian French: 0x0C0C Canadian French 177 | LngSwissFrench = LangID(0x100C) // LngSwiss French: 0x100C Swiss French 178 | ) 179 | -------------------------------------------------------------------------------- /module/goversioninfo/structbuild.go: -------------------------------------------------------------------------------- 1 | package goversioninfo 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // ***************************************************************************** 8 | // Structure Building 9 | // ***************************************************************************** 10 | 11 | /* 12 | Version Information Structures 13 | http://msdn.microsoft.com/en-us/library/windows/desktop/ff468916.aspx 14 | 15 | VersionInfo Names 16 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa381058.aspx#string-name 17 | 18 | Translation: LangID 19 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa381058.aspx#langid 20 | 21 | Translation: CharsetID 22 | http://msdn.microsoft.com/en-us/library/windows/desktop/aa381058.aspx#charsetid 23 | 24 | */ 25 | 26 | // VSVersionInfo is the top level version container. 27 | type VSVersionInfo struct { 28 | WLength uint16 29 | WValueLength uint16 30 | WType uint16 31 | SzKey []byte 32 | Padding1 []byte 33 | Value VSFixedFileInfo 34 | Padding2 []byte 35 | Children VSStringFileInfo 36 | Children2 VSVarFileInfo 37 | } 38 | 39 | // VSFixedFileInfo - most of these should be left at the defaults. 40 | type VSFixedFileInfo struct { 41 | DwSignature uint32 42 | DwStrucVersion uint32 43 | DwFileVersionMS uint32 44 | DwFileVersionLS uint32 45 | DwProductVersionMS uint32 46 | DwProductVersionLS uint32 47 | DwFileFlagsMask uint32 48 | DwFileFlags uint32 49 | DwFileOS uint32 50 | DwFileType uint32 51 | DwFileSubtype uint32 52 | DwFileDateMS uint32 53 | DwFileDateLS uint32 54 | } 55 | 56 | // VSStringFileInfo holds multiple collections of keys and values, 57 | // only allows for 1 collection in this package. 58 | type VSStringFileInfo struct { 59 | WLength uint16 60 | WValueLength uint16 61 | WType uint16 62 | SzKey []byte 63 | Padding []byte 64 | Children VSStringTable 65 | } 66 | 67 | // VSStringTable holds a collection of string keys and values. 68 | type VSStringTable struct { 69 | WLength uint16 70 | WValueLength uint16 71 | WType uint16 72 | SzKey []byte 73 | Padding []byte 74 | Children []VSString 75 | } 76 | 77 | // VSString holds the keys and values. 78 | type VSString struct { 79 | WLength uint16 80 | WValueLength uint16 81 | WType uint16 82 | SzKey []byte 83 | Padding []byte 84 | Value []byte 85 | } 86 | 87 | // VSVarFileInfo holds the translation collection of 1. 88 | type VSVarFileInfo struct { 89 | WLength uint16 90 | WValueLength uint16 91 | WType uint16 92 | SzKey []byte 93 | Padding []byte 94 | Value VSVar 95 | } 96 | 97 | // VSVar holds the translation key. 98 | type VSVar struct { 99 | WLength uint16 100 | WValueLength uint16 101 | WType uint16 102 | SzKey []byte 103 | Padding []byte 104 | Value uint32 105 | } 106 | 107 | func buildString(i int, v reflect.Value) (VSString, bool) { 108 | sValue := string(v.Field(i).Interface().(string)) 109 | sName := v.Type().Field(i).Name 110 | 111 | ss := VSString{} 112 | 113 | // If the value is set 114 | if sValue != "" { 115 | // 0 for binary, 1 for text 116 | ss.WType = 0x01 117 | 118 | // Create key 119 | ss.SzKey = padString(sName, 0) 120 | 121 | // Align to 32-bit boundary 122 | soFar := 2 123 | for (len(ss.SzKey)+6+soFar)%4 != 0 { 124 | soFar += 2 125 | } 126 | ss.Padding = padBytes(soFar) 127 | soFar += len(ss.SzKey) 128 | 129 | // Align zeros to 32-bit boundary 130 | zeros := 2 131 | for (6+soFar+(len(padString(sValue, 0)))+zeros)%4 != 0 { 132 | zeros += 2 133 | } 134 | 135 | // Create value 136 | ss.Value = padString(sValue, zeros) 137 | 138 | // Length of text in words (2 bytes) plus zero terminate word 139 | ss.WValueLength = uint16(len(padString(sValue, 0))/2) + 1 140 | 141 | // Length of structure 142 | //ss.WLength = 6 + uint16(soFar) + (ss.WValueLength * 2) 143 | ss.WLength = uint16(6 + soFar + len(ss.Value)) 144 | 145 | return ss, true 146 | } 147 | 148 | return ss, false 149 | } 150 | 151 | func buildStringTable(vi *VersionInfo) VSStringTable { 152 | st := VSStringTable{} 153 | 154 | // Always set to 0 155 | st.WValueLength = 0x00 156 | 157 | // 0 for binary, 1 for text 158 | st.WType = 0x01 159 | 160 | // Language identifier and Code page 161 | st.SzKey = padString(vi.VarFileInfo.Translation.getTranslationString(), 0) 162 | 163 | // Align to 32-bit boundary 164 | soFar := 2 165 | for (len(st.SzKey)+6+soFar)%4 != 0 { 166 | soFar += 2 167 | } 168 | st.Padding = padBytes(soFar) 169 | soFar += len(st.SzKey) 170 | 171 | // Loop through the struct fields 172 | v := reflect.ValueOf(vi.StringFileInfo) 173 | for i := 0; i < v.NumField(); i++ { 174 | // If the struct is valid 175 | if r, ok := buildString(i, v); ok { 176 | st.Children = append(st.Children, r) 177 | st.WLength += r.WLength 178 | } 179 | } 180 | 181 | st.WLength += 6 + uint16(soFar) 182 | 183 | return st 184 | } 185 | 186 | func buildStringFileInfo(vi *VersionInfo) VSStringFileInfo { 187 | sf := VSStringFileInfo{} 188 | 189 | // Always set to 0 190 | sf.WValueLength = 0x00 191 | 192 | // 0 for binary, 1 for text 193 | sf.WType = 0x01 194 | 195 | sf.SzKey = padString("StringFileInfo", 0) 196 | 197 | // Align to 32-bit boundary 198 | soFar := 2 199 | for (len(sf.SzKey)+6+soFar)%4 != 0 { 200 | soFar += 2 201 | } 202 | sf.Padding = padBytes(soFar) 203 | soFar += len(sf.SzKey) 204 | 205 | // Allows for more than one string table 206 | st := buildStringTable(vi) 207 | sf.Children = st 208 | 209 | sf.WLength = 6 + uint16(soFar) + st.WLength 210 | 211 | return sf 212 | } 213 | 214 | func buildVar(vfi VarFileInfo) VSVar { 215 | vs := VSVar{} 216 | 217 | // 0 for binary, 1 for text 218 | vs.WType = 0x00 219 | 220 | // Create key 221 | vs.SzKey = padString("Translation", 0) 222 | 223 | // Align to 32-bit boundary 224 | soFar := 2 225 | for (len(vs.SzKey)+6+soFar)%4 != 0 { 226 | soFar += 2 227 | } 228 | vs.Padding = padBytes(soFar) 229 | soFar += len(vs.SzKey) 230 | 231 | // Create value 232 | vs.Value = str2Uint32(vfi.Translation.getTranslation()) 233 | 234 | // Length of text in bytes 235 | vs.WValueLength = 4 236 | 237 | // Length of structure 238 | vs.WLength = 6 + vs.WValueLength + uint16(soFar) 239 | 240 | return vs 241 | } 242 | 243 | func buildVarFileInfo(vfi VarFileInfo) VSVarFileInfo { 244 | vf := VSVarFileInfo{} 245 | 246 | // Always set to 0 247 | vf.WValueLength = 0x00 248 | 249 | // 0 for binary, 1 for text 250 | vf.WType = 0x01 251 | 252 | vf.SzKey = padString("VarFileInfo", 0) 253 | 254 | // Align to 32-bit boundary 255 | soFar := 2 256 | for (len(vf.SzKey)+6+soFar)%4 != 0 { 257 | soFar += 2 258 | } 259 | vf.Padding = padBytes(soFar) 260 | soFar += len(vf.SzKey) 261 | 262 | // TODO Allow for more than one var table 263 | st := buildVar(vfi) 264 | vf.Value = st 265 | vf.WLength = 6 + st.WLength + uint16(soFar) 266 | 267 | return vf 268 | } 269 | 270 | func buildFixedFileInfo(vi *VersionInfo) VSFixedFileInfo { 271 | ff := VSFixedFileInfo{} 272 | ff.DwSignature = 0xFEEF04BD 273 | ff.DwStrucVersion = 0x00010000 274 | ff.DwFileVersionMS = str2Uint32(vi.FixedFileInfo.FileVersion.getVersionHighString()) 275 | ff.DwFileVersionLS = str2Uint32(vi.FixedFileInfo.FileVersion.getVersionLowString()) 276 | ff.DwProductVersionMS = str2Uint32(vi.FixedFileInfo.ProductVersion.getVersionHighString()) 277 | ff.DwProductVersionLS = str2Uint32(vi.FixedFileInfo.ProductVersion.getVersionLowString()) 278 | ff.DwFileFlagsMask = str2Uint32(vi.FixedFileInfo.FileFlagsMask) 279 | ff.DwFileFlags = str2Uint32(vi.FixedFileInfo.FileFlags) 280 | ff.DwFileOS = str2Uint32(vi.FixedFileInfo.FileOS) 281 | ff.DwFileType = str2Uint32(vi.FixedFileInfo.FileType) 282 | ff.DwFileSubtype = str2Uint32(vi.FixedFileInfo.FileSubType) 283 | 284 | // According to the spec, these should be zero...ugh 285 | /*if vi.Timestamp { 286 | now := syscall.NsecToFiletime(time.Now().UnixNano()) 287 | ff.DwFileDateMS = now.HighDateTime 288 | ff.DwFileDateLS = now.LowDateTime 289 | }*/ 290 | 291 | return ff 292 | } 293 | 294 | // Build fills the structs with data from the config file 295 | func (v *VersionInfo) Build() { 296 | vi := VSVersionInfo{} 297 | 298 | // 0 for binary, 1 for text 299 | vi.WType = 0x00 300 | 301 | vi.SzKey = padString("VS_VERSION_INFO", 0) 302 | 303 | // Align to 32-bit boundary 304 | // 6 is for the size of WLength, WValueLength, and WType (each is 1 word or 2 bytes: FF FF) 305 | soFar := 2 306 | for (len(vi.SzKey)+6+soFar)%4 != 0 { 307 | soFar += 2 308 | } 309 | vi.Padding1 = padBytes(soFar) 310 | soFar += len(vi.SzKey) 311 | 312 | vi.Value = buildFixedFileInfo(v) 313 | 314 | // Length of VSFixedFileInfo (always the same) 315 | vi.WValueLength = 0x34 316 | 317 | // Never needs padding, not included in WLength 318 | vi.Padding2 = []byte{} 319 | 320 | // Build strings 321 | vi.Children = buildStringFileInfo(v) 322 | 323 | // Build translation 324 | vi.Children2 = buildVarFileInfo(v.VarFileInfo) 325 | 326 | // Calculate the total size 327 | vi.WLength += 6 + uint16(soFar) + vi.WValueLength + vi.Children.WLength + vi.Children2.WLength 328 | 329 | v.structure = vi 330 | } 331 | -------------------------------------------------------------------------------- /module/rsrc/.hgignore: -------------------------------------------------------------------------------- 1 | glob:*.res 2 | glob:*.exe 3 | 4 | glob:tmp 5 | -------------------------------------------------------------------------------- /module/rsrc/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | jobs: 3 | allow_failures: 4 | - go: master 5 | include: 6 | - name: -- Default linux build 7 | go: 1.x 8 | os: linux 9 | arch: amd64 10 | - name: -- Default windows build 11 | go: 1.x 12 | os: windows 13 | arch: amd64 14 | - name: -- Go devel - check future version breakage 15 | go: master 16 | os: linux 17 | arch: amd64 18 | - name: -- Ubuntu/IBM (?) cares for ppc64le? see https://github.com/akavel/rsrc/pull/31 19 | go: 1.x 20 | os: linux 21 | arch: ppc64le 22 | 23 | before_install: 24 | - |- 25 | case $TRAVIS_OS_NAME in 26 | windows) 27 | choco install upx 28 | ;; 29 | esac 30 | 31 | -------------------------------------------------------------------------------- /module/rsrc/AUTHORS: -------------------------------------------------------------------------------- 1 | Force Charlie 2 | Mateusz Czapliński 3 | Quentin Renard 4 | shnmng 5 | Thomas Combeléran 6 | -------------------------------------------------------------------------------- /module/rsrc/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 The rsrc Authors. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /module/rsrc/README.txt: -------------------------------------------------------------------------------- 1 | rsrc - Tool for embedding binary resources in Go programs. 2 | 3 | INSTALL: go get github.com/akavel/rsrc 4 | 5 | USAGE: 6 | 7 | rsrc.exe [-manifest FILE.exe.manifest] [-ico FILE.ico[,FILE2.ico...]] [OPTIONS...] 8 | Generates a .syso file with specified resources embedded in .rsrc section, 9 | aimed for consumption by Go linker when building Win32 excecutables. 10 | 11 | The generated *.syso files should get automatically recognized by 'go build' 12 | command and linked into an executable/library, as long as there are any *.go 13 | files in the same directory. 14 | 15 | OPTIONS: 16 | -arch string 17 | architecture of output file - one of: 386, amd64, [EXPERIMENTAL: arm, arm64] (default "amd64") 18 | -ico string 19 | comma-separated list of paths to .ico files to embed 20 | -manifest string 21 | path to a Windows manifest file to embed 22 | -o string 23 | name of output COFF (.res or .syso) file; if set to empty, will default to 'rsrc_windows_{arch}.syso' 24 | 25 | Based on ideas presented by Minux. 26 | 27 | In case anything does not work, it'd be nice if you could report (either via Github 28 | issues, or via email to czapkofan@gmail.com), and please attach the input file(s) 29 | which resulted in a problem, plus error message & symptoms, and/or any other details. 30 | 31 | TODO MAYBE/LATER: 32 | - fix or remove FIXMEs 33 | 34 | LICENSE: MIT 35 | Copyright 2013-2020 The rsrc Authors. 36 | 37 | http://github.com/akavel/rsrc 38 | 39 | (NOTE: This project is currently in low-priority maintenance mode. I welcome 40 | bug reports and sometimes try to address them, but this happens very randomly. 41 | If it works for you, that's great and I'm happy! Still, and especially if not, 42 | you might like to check the following more featureful alternative from @tc-hib 43 | who is a very nice guy: 44 | https://github.com/tc-hib/go-winres) 45 | 46 | -------------------------------------------------------------------------------- /module/rsrc/VERSION: -------------------------------------------------------------------------------- 1 | https://github.com/akavel/rsrc 2 | 936343600d578fb5da472acf6987ed76935a389b -------------------------------------------------------------------------------- /module/rsrc/binutil/plain.go: -------------------------------------------------------------------------------- 1 | package binutil 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func Plain(kind reflect.Kind) bool { 8 | switch kind { 9 | case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 10 | return true 11 | } 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /module/rsrc/binutil/sizedfile.go: -------------------------------------------------------------------------------- 1 | package binutil 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | type SizedReader interface { 9 | io.Reader 10 | Size() int64 11 | } 12 | 13 | type SizedFile struct { 14 | f *os.File 15 | s *io.SectionReader // helper, for Size() 16 | } 17 | 18 | func (r *SizedFile) Read(p []byte) (n int, err error) { return r.s.Read(p) } 19 | func (r *SizedFile) Size() int64 { return r.s.Size() } 20 | func (r *SizedFile) Close() error { return r.f.Close() } 21 | 22 | func SizedOpen(filename string) (*SizedFile, error) { 23 | f, err := os.Open(filename) 24 | if err != nil { 25 | return nil, err 26 | } 27 | info, err := f.Stat() 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &SizedFile{ 32 | f: f, 33 | s: io.NewSectionReader(f, 0, info.Size()), 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /module/rsrc/binutil/walk.go: -------------------------------------------------------------------------------- 1 | package binutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path" 7 | "reflect" 8 | ) 9 | 10 | var ( 11 | WALK_SKIP = errors.New("") 12 | ) 13 | 14 | type Walker func(v reflect.Value, path string) error 15 | 16 | func Walk(value interface{}, walker Walker) error { 17 | err := walk(reflect.ValueOf(value), "/", walker) 18 | if err == WALK_SKIP { 19 | err = nil 20 | } 21 | return err 22 | } 23 | 24 | func stopping(err error) bool { 25 | return err != nil && err != WALK_SKIP 26 | } 27 | 28 | func walk(v reflect.Value, spath string, walker Walker) error { 29 | err := walker(v, spath) 30 | if err != nil { 31 | return err 32 | } 33 | v = reflect.Indirect(v) 34 | switch v.Kind() { 35 | case reflect.Slice, reflect.Array: 36 | for i := 0; i < v.Len(); i++ { 37 | err = walk(v.Index(i), spath+fmt.Sprintf("[%d]", i), walker) 38 | if stopping(err) { 39 | return err 40 | } 41 | } 42 | case reflect.Interface: 43 | err = walk(v.Elem(), spath, walker) 44 | if stopping(err) { 45 | return err 46 | } 47 | case reflect.Struct: 48 | //t := v.Type() 49 | for i := 0; i < v.NumField(); i++ { 50 | //f := t.Field(i) //TODO: handle unexported fields 51 | vv := v.Field(i) 52 | err = walk(vv, path.Join(spath, v.Type().Field(i).Name), walker) 53 | if stopping(err) { 54 | return err 55 | } 56 | } 57 | default: 58 | // FIXME: handle other special cases too 59 | // String 60 | return nil 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /module/rsrc/binutil/writer.go: -------------------------------------------------------------------------------- 1 | package binutil 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | type Writer struct { 10 | W io.Writer 11 | Offset uint32 //FIXME: int64? 12 | Err error 13 | } 14 | 15 | func (w *Writer) WriteLE(v interface{}) { 16 | if w.Err != nil { 17 | return 18 | } 19 | w.Err = binary.Write(w.W, binary.LittleEndian, v) 20 | if w.Err != nil { 21 | return 22 | } 23 | w.Offset += uint32(reflect.TypeOf(v).Size()) 24 | } 25 | 26 | func (w *Writer) WriteFromSized(r SizedReader) { 27 | if w.Err != nil { 28 | return 29 | } 30 | var n int64 31 | n, w.Err = io.CopyN(w.W, r, r.Size()) 32 | w.Offset += uint32(n) 33 | } 34 | -------------------------------------------------------------------------------- /module/rsrc/buildcross.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem NOTE: see also: 3 | rem https://github.com/golang/go/wiki/WindowsCrossCompiling 4 | rem https://github.com/golang/go/wiki/InstallFromSource#install-c-tools 5 | call :build rsrc windows_386 6 | call :build rsrc windows_amd64 7 | call :build rsrc linux_amd64 8 | call :build rsrc darwin_amd64 9 | set GOOS= 10 | set GOARCH= 11 | goto :eof 12 | 13 | :build 14 | set APP=%1 15 | set PLATFORM=%2 16 | :: Split param into GOOS & GOARCH (see: http://ss64.com/nt/syntax-substring.html) 17 | set GOARCH=%PLATFORM:*_=% 18 | call set GOOS=%%PLATFORM:_%GOARCH%=%% 19 | :: Build filename 20 | set FNAME=%APP%_%PLATFORM% 21 | if "%GOOS%"=="windows" set FNAME=%FNAME%.exe 22 | :: Do the build 23 | echo == %FNAME% == 24 | go build -i -v -o %FNAME% . 25 | goto :eof 26 | 27 | -------------------------------------------------------------------------------- /module/rsrc/coff/coff.go: -------------------------------------------------------------------------------- 1 | package coff 2 | 3 | import ( 4 | "debug/pe" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "reflect" 9 | "regexp" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/balibuild/bali/v3/module/rsrc/binutil" 15 | ) 16 | 17 | type Dir struct { // struct IMAGE_RESOURCE_DIRECTORY 18 | Characteristics uint32 19 | TimeDateStamp uint32 20 | MajorVersion uint16 21 | MinorVersion uint16 22 | NumberOfNamedEntries uint16 23 | NumberOfIdEntries uint16 24 | DirEntries 25 | Dirs 26 | } 27 | 28 | type DirEntries []DirEntry 29 | type Dirs []Dir 30 | 31 | type DirEntry struct { // struct IMAGE_RESOURCE_DIRECTORY_ENTRY 32 | NameOrId uint32 33 | OffsetToData uint32 34 | } 35 | 36 | type DataEntry struct { // struct IMAGE_RESOURCE_DATA_ENTRY 37 | OffsetToData uint32 38 | Size1 uint32 39 | CodePage uint32 //FIXME: what value here? for now just using 0 40 | Reserved uint32 41 | } 42 | 43 | type RelocationEntry struct { 44 | RVA uint32 // "offset within the Section's raw data where the address starts." 45 | SymbolIndex uint32 // "(zero based) index in the Symbol table to which the reference refers." 46 | Type uint16 47 | } 48 | 49 | // Values reverse-engineered from windres output; names from teh Internets. 50 | // Teh googlies Internets don't seem to have much to say about the AMD64 one, 51 | // unfortunately :/ but it works... 52 | const ( 53 | _IMAGE_REL_AMD64_ADDR32NB = 0x03 54 | _IMAGE_REL_I386_DIR32NB = 0x07 55 | _IMAGE_REL_ARM64_ADDR32NB = 0x02 56 | _IMAGE_REL_ARM_ADDR32NB = 0x02 57 | ) 58 | 59 | type Auxiliary [18]byte 60 | 61 | type Symbol struct { 62 | Name [8]byte 63 | Value uint32 64 | SectionNumber uint16 65 | Type uint16 66 | StorageClass uint8 67 | AuxiliaryCount uint8 68 | Auxiliaries []Auxiliary 69 | } 70 | 71 | type StringsHeader struct { 72 | Length uint32 73 | } 74 | 75 | const ( 76 | MASK_SUBDIRECTORY = 1 << 31 77 | 78 | RT_ICON = 3 79 | RT_GROUP_ICON = 3 + 11 80 | RT_MANIFEST = 24 81 | ) 82 | 83 | // http://www.delorie.com/djgpp/doc/coff/symtab.html 84 | const ( 85 | DT_PTR = 1 86 | T_UCHAR = 12 87 | ) 88 | 89 | var ( 90 | STRING_RSRC = [8]byte{'.', 'r', 's', 'r', 'c', 0, 0, 0} 91 | 92 | LANG_ENTRY = DirEntry{NameOrId: 0x0409} //FIXME: language; what value should be here? 93 | ) 94 | 95 | type PaddedData struct { 96 | Data Sizer 97 | Padding []byte 98 | } 99 | 100 | type Sizer interface { 101 | Size() int64 //NOTE: must not exceed limits of uint32, or behavior is undefined 102 | } 103 | 104 | type Coff struct { 105 | pe.FileHeader 106 | pe.SectionHeader32 107 | 108 | *Dir 109 | DataEntries []DataEntry 110 | Data []PaddedData 111 | 112 | Relocations []RelocationEntry 113 | Symbols []Symbol 114 | StringsHeader 115 | Strings []Sizer 116 | } 117 | 118 | // NOTE: must be called immediately after NewRSRC, before any other 119 | // functions. 120 | func (coff *Coff) Arch(arch string) error { 121 | switch arch { 122 | case "386": 123 | coff.Machine = pe.IMAGE_FILE_MACHINE_I386 124 | case "amd64": 125 | // Sources: 126 | // https://github.com/golang/go/blob/0e23ca41d99c82d301badf1b762888e2c69e6c57/src/debug/pe/pe.go#L116 127 | // https://github.com/yasm/yasm/blob/7160679eee91323db98b0974596c7221eeff772c/modules/objfmts/coff/coff-objfmt.c#L38 128 | // FIXME: currently experimental -- not sure if something more doesn't need to be changed 129 | coff.Machine = pe.IMAGE_FILE_MACHINE_AMD64 130 | case "arm": 131 | // see 132 | // https://github.com/golang/go/blob/f3424ceff2fa48615ed98580f337ab044925c940/src/cmd/link/internal/ld/pe.go#L736 133 | coff.Machine = pe.IMAGE_FILE_MACHINE_ARMNT 134 | case "arm64": 135 | // Waiting https://github.com/golang/go/issues/36439 136 | coff.Machine = pe.IMAGE_FILE_MACHINE_ARM64 137 | default: 138 | return errors.New("coff: unknown architecture: " + arch) 139 | } 140 | return nil 141 | } 142 | 143 | // addSymbol appends a symbol to Coff.Symbols and to Coff.Strings. 144 | // NOTE: symbol s must be probably >8 characters long 145 | // NOTE: symbol s should not contain embedded zeroes 146 | func (coff *Coff) addSymbol(s string) { 147 | coff.FileHeader.NumberOfSymbols++ 148 | 149 | buf := strings.NewReader(s + "\000") // ASCIIZ 150 | r := io.NewSectionReader(buf, 0, int64(len(s)+1)) 151 | coff.Strings = append(coff.Strings, r) 152 | 153 | coff.StringsHeader.Length += uint32(r.Size()) 154 | 155 | coff.Symbols = append(coff.Symbols, Symbol{ 156 | //Name: // will be filled in Freeze 157 | //Value: // as above 158 | SectionNumber: 1, 159 | Type: 0, // why 0??? // DT_PTR<<4 | T_UCHAR, // unsigned char* // (?) or use void* ? T_VOID=1 160 | StorageClass: 2, // 2=C_EXT, or 5=C_EXTDEF ? 161 | AuxiliaryCount: 0, 162 | }) 163 | } 164 | 165 | func NewRSRC() *Coff { 166 | return &Coff{ 167 | pe.FileHeader{ 168 | Machine: pe.IMAGE_FILE_MACHINE_I386, 169 | NumberOfSections: 1, // .rsrc 170 | TimeDateStamp: 0, // was also 0 in sample data from MinGW's windres.exe 171 | NumberOfSymbols: 1, 172 | SizeOfOptionalHeader: 0, 173 | Characteristics: 0x0104, //FIXME: copied from windres.exe output, find out what should be here and why 174 | }, 175 | pe.SectionHeader32{ 176 | Name: STRING_RSRC, 177 | Characteristics: 0x40000040, // "INITIALIZED_DATA MEM_READ" ? 178 | }, 179 | 180 | // "directory hierarchy" of .rsrc section: top level goes resource type, then id/name, then language 181 | &Dir{}, 182 | 183 | []DataEntry{}, 184 | []PaddedData{}, 185 | 186 | []RelocationEntry{}, 187 | 188 | []Symbol{{ 189 | Name: STRING_RSRC, 190 | Value: 0, 191 | SectionNumber: 1, 192 | Type: 0, // FIXME: wtf? 193 | StorageClass: 3, // FIXME: is it ok? and uint8? and what does the value mean? 194 | AuxiliaryCount: 0, // FIXME: wtf? 195 | }}, 196 | 197 | StringsHeader{ 198 | Length: uint32(binary.Size(StringsHeader{})), // empty strings table -- but we must still show size of the table's header... 199 | }, 200 | []Sizer{}, 201 | } 202 | } 203 | 204 | // NOTE: function assumes that 'id' is increasing on each entry 205 | // NOTE: only usable for Coff created using NewRSRC 206 | func (coff *Coff) AddResource(kind uint32, id uint16, data Sizer) { 207 | re := RelocationEntry{ 208 | // "(zero based) index in the Symbol table to which the 209 | // reference refers. Once you have loaded the COFF file into 210 | // memory and know where each symbol is, you find the new 211 | // updated address for the given symbol and update the 212 | // reference accordingly." 213 | SymbolIndex: 0, 214 | } 215 | switch coff.Machine { 216 | case pe.IMAGE_FILE_MACHINE_I386: 217 | re.Type = _IMAGE_REL_I386_DIR32NB 218 | case pe.IMAGE_FILE_MACHINE_AMD64: 219 | re.Type = _IMAGE_REL_AMD64_ADDR32NB 220 | case pe.IMAGE_FILE_MACHINE_ARMNT: 221 | re.Type = _IMAGE_REL_ARM_ADDR32NB 222 | case pe.IMAGE_FILE_MACHINE_ARM64: 223 | re.Type = _IMAGE_REL_ARM64_ADDR32NB 224 | } 225 | coff.Relocations = append(coff.Relocations, re) 226 | coff.SectionHeader32.NumberOfRelocations++ 227 | 228 | // find top level entry, inserting new if necessary at correct sorted position 229 | entries0 := coff.Dir.DirEntries 230 | dirs0 := coff.Dir.Dirs 231 | i0 := sort.Search(len(entries0), func(i int) bool { 232 | return entries0[i].NameOrId >= kind 233 | }) 234 | if i0 >= len(entries0) || entries0[i0].NameOrId != kind { 235 | // inserting new entry & dir 236 | entries0 = append(entries0[:i0], append([]DirEntry{{NameOrId: kind}}, entries0[i0:]...)...) 237 | dirs0 = append(dirs0[:i0], append([]Dir{{}}, dirs0[i0:]...)...) 238 | coff.Dir.NumberOfIdEntries++ 239 | } 240 | coff.Dir.DirEntries = entries0 241 | coff.Dir.Dirs = dirs0 242 | 243 | // for second level, assume ID is always increasing, so we don't have to sort 244 | dirs0[i0].DirEntries = append(dirs0[i0].DirEntries, DirEntry{NameOrId: uint32(id)}) 245 | dirs0[i0].Dirs = append(dirs0[i0].Dirs, Dir{ 246 | NumberOfIdEntries: 1, 247 | DirEntries: DirEntries{LANG_ENTRY}, 248 | }) 249 | dirs0[i0].NumberOfIdEntries++ 250 | 251 | // calculate preceding DirEntry leaves, to find new index in Data & DataEntries 252 | n := 0 253 | for _, dir0 := range dirs0[:i0+1] { 254 | n += len(dir0.DirEntries) //NOTE: assuming 1 language here; TODO: dwell deeper if more langs added 255 | } 256 | n-- 257 | 258 | // insert new data in correct place 259 | coff.DataEntries = append(coff.DataEntries[:n], append([]DataEntry{{Size1: uint32(data.Size())}}, coff.DataEntries[n:]...)...) 260 | coff.Data = append(coff.Data[:n], append([]PaddedData{pad(data)}, coff.Data[n:]...)...) 261 | } 262 | 263 | func pad(data Sizer) PaddedData { 264 | return PaddedData{ 265 | Data: data, 266 | Padding: make([]byte, -data.Size()&7), 267 | } 268 | } 269 | 270 | // Freeze fills in some important offsets in resulting file. 271 | func (coff *Coff) Freeze() { 272 | switch coff.SectionHeader32.Name { 273 | case STRING_RSRC: 274 | coff.freezeRSRC() 275 | } 276 | } 277 | 278 | func (coff *Coff) freezeCommon1(path string, offset, diroff uint32) (newdiroff uint32) { 279 | switch path { 280 | case "/Dir": 281 | coff.SectionHeader32.PointerToRawData = offset 282 | diroff = offset 283 | case "/Relocations": 284 | coff.SectionHeader32.PointerToRelocations = offset 285 | coff.SectionHeader32.SizeOfRawData = offset - diroff 286 | case "/Symbols": 287 | coff.FileHeader.PointerToSymbolTable = offset 288 | } 289 | return diroff 290 | } 291 | 292 | func freezeCommon2(v reflect.Value, offset *uint32) error { 293 | if binutil.Plain(v.Kind()) { 294 | *offset += uint32(binary.Size(v.Interface())) // TODO: change to v.Type().Size() ? 295 | return nil 296 | } 297 | vv, ok := v.Interface().(Sizer) 298 | if ok { 299 | *offset += uint32(vv.Size()) 300 | return binutil.WALK_SKIP 301 | } 302 | return nil 303 | } 304 | 305 | func (coff *Coff) freezeRSRC() { 306 | leafwalker := make(chan *DirEntry) 307 | go func() { 308 | for _, dir1 := range coff.Dir.Dirs { // resource type 309 | for _, dir2 := range dir1.Dirs { // resource ID 310 | for i := range dir2.DirEntries { // resource lang 311 | leafwalker <- &dir2.DirEntries[i] 312 | } 313 | } 314 | } 315 | }() 316 | 317 | var offset, diroff uint32 318 | binutil.Walk(coff, func(v reflect.Value, path string) error { 319 | diroff = coff.freezeCommon1(path, offset, diroff) 320 | 321 | RE := regexp.MustCompile 322 | const N = `\[(\d+)\]` 323 | m := matcher{} 324 | switch { 325 | case m.Find(path, RE("^/Dir/Dirs"+N+"$")): 326 | coff.Dir.DirEntries[m[0]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff) 327 | case m.Find(path, RE("^/Dir/Dirs"+N+"/Dirs"+N+"$")): 328 | coff.Dir.Dirs[m[0]].DirEntries[m[1]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff) 329 | case m.Find(path, RE("^/DataEntries"+N+"$")): 330 | direntry := <-leafwalker 331 | direntry.OffsetToData = offset - diroff 332 | case m.Find(path, RE("^/DataEntries"+N+"/OffsetToData$")): 333 | coff.Relocations[m[0]].RVA = offset - diroff 334 | case m.Find(path, RE("^/Data"+N+"$")): 335 | coff.DataEntries[m[0]].OffsetToData = offset - diroff 336 | } 337 | 338 | return freezeCommon2(v, &offset) 339 | }) 340 | } 341 | 342 | func mustAtoi(s string) int { 343 | i, err := strconv.Atoi(s) 344 | if err != nil { 345 | panic(err) 346 | } 347 | return i 348 | } 349 | 350 | type matcher []int 351 | 352 | func (m *matcher) Find(s string, re *regexp.Regexp) bool { 353 | subs := re.FindStringSubmatch(s) 354 | if subs == nil { 355 | return false 356 | } 357 | 358 | *m = (*m)[:0] 359 | for i := 1; i < len(subs); i++ { 360 | *m = append(*m, mustAtoi(subs[i])) 361 | } 362 | return true 363 | } 364 | -------------------------------------------------------------------------------- /module/rsrc/ico/ico.go: -------------------------------------------------------------------------------- 1 | // Package ico describes Windows ICO file format. 2 | package ico 3 | 4 | // ICO: http://msdn.microsoft.com/en-us/library/ms997538.aspx 5 | // BMP/DIB: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183562%28v=vs.85%29.aspx 6 | 7 | import ( 8 | "bytes" 9 | "encoding/binary" 10 | "fmt" 11 | "image" 12 | "image/color" 13 | "io" 14 | "sort" 15 | ) 16 | 17 | const ( 18 | BI_RGB = 0 19 | ) 20 | 21 | type ICONDIR struct { 22 | Reserved uint16 // must be 0 23 | Type uint16 // Resource Type (1 for icons) 24 | Count uint16 // How many images? 25 | } 26 | 27 | type IconDirEntryCommon struct { 28 | Width byte // Width, in pixels, of the image 29 | Height byte // Height, in pixels, of the image 30 | ColorCount byte // Number of colors in image (0 if >=8bpp) 31 | Reserved byte // Reserved (must be 0) 32 | Planes uint16 // Color Planes 33 | BitCount uint16 // Bits per pixel 34 | BytesInRes uint32 // How many bytes in this resource? 35 | } 36 | 37 | type ICONDIRENTRY struct { 38 | IconDirEntryCommon 39 | ImageOffset uint32 // Where in the file is this image? [from beginning of file] 40 | } 41 | 42 | type BITMAPINFOHEADER struct { 43 | Size uint32 44 | Width int32 45 | Height int32 // NOTE: "represents the combined height of the XOR and AND masks. Remember to divide this number by two before using it to perform calculations for either of the XOR or AND masks." 46 | Planes uint16 // [BMP/DIB]: "is always 1" 47 | BitCount uint16 48 | Compression uint32 // for ico = 0 49 | SizeImage uint32 50 | XPelsPerMeter int32 // for ico = 0 51 | YPelsPerMeter int32 // for ico = 0 52 | ClrUsed uint32 // for ico = 0 53 | ClrImportant uint32 // for ico = 0 54 | } 55 | 56 | type RGBQUAD struct { 57 | Blue byte 58 | Green byte 59 | Red byte 60 | Reserved byte // must be 0 61 | } 62 | 63 | func skip(r io.Reader, n int64) error { 64 | _, err := io.CopyN(io.Discard, r, n) 65 | return err 66 | } 67 | 68 | type icoOffset struct { 69 | n int 70 | offset uint32 71 | } 72 | 73 | type rawico struct { 74 | icoinfo ICONDIRENTRY 75 | bmpinfo *BITMAPINFOHEADER 76 | idx int 77 | data []byte 78 | } 79 | 80 | type byOffsets []rawico 81 | 82 | func (o byOffsets) Len() int { return len(o) } 83 | func (o byOffsets) Less(i, j int) bool { return o[i].icoinfo.ImageOffset < o[j].icoinfo.ImageOffset } 84 | func (o byOffsets) Swap(i, j int) { 85 | tmp := o[i] 86 | o[i] = o[j] 87 | o[j] = tmp 88 | } 89 | 90 | type ICO struct { 91 | image.Image 92 | } 93 | 94 | func DecodeHeaders(r io.Reader) ([]ICONDIRENTRY, error) { 95 | var hdr ICONDIR 96 | err := binary.Read(r, binary.LittleEndian, &hdr) 97 | if err != nil { 98 | return nil, err 99 | } 100 | if hdr.Reserved != 0 || hdr.Type != 1 { 101 | return nil, fmt.Errorf("bad magic number") 102 | } 103 | 104 | entries := make([]ICONDIRENTRY, hdr.Count) 105 | for i := 0; i < len(entries); i++ { 106 | err = binary.Read(r, binary.LittleEndian, &entries[i]) 107 | if err != nil { 108 | return nil, err 109 | } 110 | } 111 | return entries, nil 112 | } 113 | 114 | // NOTE: won't succeed on files with overlapping offsets 115 | func unused_decodeAll(r io.Reader) ([]*ICO, error) { 116 | var hdr ICONDIR 117 | err := binary.Read(r, binary.LittleEndian, &hdr) 118 | if err != nil { 119 | return nil, err 120 | } 121 | if hdr.Reserved != 0 || hdr.Type != 1 { 122 | return nil, fmt.Errorf("bad magic number") 123 | } 124 | 125 | raws := make([]rawico, hdr.Count) 126 | for i := 0; i < len(raws); i++ { 127 | err = binary.Read(r, binary.LittleEndian, &raws[i].icoinfo) 128 | if err != nil { 129 | return nil, err 130 | } 131 | raws[i].idx = i 132 | } 133 | 134 | sort.Sort(byOffsets(raws)) 135 | 136 | offset := uint32(binary.Size(&hdr) + len(raws)*binary.Size(ICONDIRENTRY{})) 137 | for i := 0; i < len(raws); i++ { 138 | err = skip(r, int64(raws[i].icoinfo.ImageOffset-offset)) 139 | if err != nil { 140 | return nil, err 141 | } 142 | offset = raws[i].icoinfo.ImageOffset 143 | 144 | raws[i].bmpinfo = &BITMAPINFOHEADER{} 145 | err = binary.Read(r, binary.LittleEndian, raws[i].bmpinfo) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | err = skip(r, int64(raws[i].bmpinfo.Size-uint32(binary.Size(BITMAPINFOHEADER{})))) 151 | if err != nil { 152 | return nil, err 153 | } 154 | raws[i].data = make([]byte, raws[i].icoinfo.BytesInRes-raws[i].bmpinfo.Size) 155 | _, err = io.ReadFull(r, raws[i].data) 156 | if err != nil { 157 | return nil, err 158 | } 159 | } 160 | 161 | icos := make([]*ICO, len(raws)) 162 | for i := 0; i < len(raws); i++ { 163 | fmt.Println(i) 164 | icos[raws[i].idx], err = decode(raws[i].bmpinfo, &raws[i].icoinfo, raws[i].data) 165 | if err != nil { 166 | return nil, err 167 | } 168 | } 169 | return icos, nil 170 | } 171 | 172 | func decode(info *BITMAPINFOHEADER, icoinfo *ICONDIRENTRY, data []byte) (*ICO, error) { 173 | if info.Compression != BI_RGB { 174 | return nil, fmt.Errorf("ICO compression not supported (got %d)", info.Compression) 175 | } 176 | 177 | //if info.ClrUsed!=0 { 178 | // panic(info.ClrUsed) 179 | //} 180 | 181 | r := bytes.NewBuffer(data) 182 | 183 | bottomup := info.Height > 0 184 | if !bottomup { 185 | info.Height = -info.Height 186 | } 187 | 188 | switch info.BitCount { 189 | case 8: 190 | ncol := int(icoinfo.ColorCount) 191 | if ncol == 0 { 192 | ncol = 256 193 | } 194 | 195 | pal := make(color.Palette, ncol) 196 | for i := 0; i < ncol; i++ { 197 | var rgb RGBQUAD 198 | err := binary.Read(r, binary.LittleEndian, &rgb) 199 | if err != nil { 200 | return nil, err 201 | } 202 | pal[i] = color.NRGBA{R: rgb.Red, G: rgb.Green, B: rgb.Blue, A: 0xff} //FIXME: is Alpha ok 0xff? 203 | } 204 | fmt.Println(pal) 205 | 206 | fmt.Println(info.SizeImage, len(data)-binary.Size(RGBQUAD{})*len(pal), info.Width, info.Height) 207 | 208 | default: 209 | return nil, fmt.Errorf("unsupported ICO bit depth (BitCount) %d", info.BitCount) 210 | } 211 | 212 | return nil, nil 213 | } 214 | -------------------------------------------------------------------------------- /module/rsrc/internal/write.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | 8 | "github.com/balibuild/bali/v3/module/rsrc/binutil" 9 | "github.com/balibuild/bali/v3/module/rsrc/coff" 10 | ) 11 | 12 | // TODO(akavel): maybe promote this to coff.Coff.WriteTo(io.Writer) (int64, error) 13 | func Write(coff *coff.Coff, fnameout string) error { 14 | out, err := os.Create(fnameout) 15 | if err != nil { 16 | return err 17 | } 18 | defer out.Close() 19 | w := binutil.Writer{W: out} 20 | 21 | // write the resulting file to disk 22 | binutil.Walk(coff, func(v reflect.Value, path string) error { 23 | if binutil.Plain(v.Kind()) { 24 | w.WriteLE(v.Interface()) 25 | return nil 26 | } 27 | vv, ok := v.Interface().(binutil.SizedReader) 28 | if ok { 29 | w.WriteFromSized(vv) 30 | return binutil.WALK_SKIP 31 | } 32 | return nil 33 | }) 34 | 35 | if w.Err != nil { 36 | return fmt.Errorf("Error writing output file: %s", w.Err) 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /module/rsrc/rsrc/rsrc.go: -------------------------------------------------------------------------------- 1 | package rsrc 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/balibuild/bali/v3/module/rsrc/binutil" 11 | "github.com/balibuild/bali/v3/module/rsrc/coff" 12 | "github.com/balibuild/bali/v3/module/rsrc/ico" 13 | "github.com/balibuild/bali/v3/module/rsrc/internal" 14 | ) 15 | 16 | // on storing icons, see: http://blogs.msdn.com/b/oldnewthing/archive/2012/07/20/10331787.aspx 17 | type _GRPICONDIR struct { 18 | ico.ICONDIR 19 | Entries []_GRPICONDIRENTRY 20 | } 21 | 22 | func (group _GRPICONDIR) Size() int64 { 23 | return int64(binary.Size(group.ICONDIR) + len(group.Entries)*binary.Size(group.Entries[0])) 24 | } 25 | 26 | type _GRPICONDIRENTRY struct { 27 | ico.IconDirEntryCommon 28 | Id uint16 29 | } 30 | 31 | func Embed(fnameout, arch, fnamein, fnameico string) error { 32 | lastid := uint16(0) 33 | newid := func() uint16 { 34 | lastid++ 35 | return lastid 36 | } 37 | 38 | out := coff.NewRSRC() 39 | err := out.Arch(arch) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | if fnamein != "" { 45 | manifest, err := binutil.SizedOpen(fnamein) 46 | if err != nil { 47 | return fmt.Errorf("rsrc: error opening manifest file '%s': %s", fnamein, err) 48 | } 49 | defer manifest.Close() 50 | 51 | id := newid() 52 | out.AddResource(coff.RT_MANIFEST, id, manifest) 53 | // TODO(akavel): reintroduce the Printlns in package main after Embed returns 54 | // fmt.Println("Manifest ID: ", id) 55 | } 56 | if fnameico != "" { 57 | for _, fnameicosingle := range strings.Split(fnameico, ",") { 58 | f, err := addIcon(out, fnameicosingle, newid) 59 | if err != nil { 60 | return err 61 | } 62 | defer f.Close() 63 | } 64 | } 65 | 66 | out.Freeze() 67 | 68 | return internal.Write(out, fnameout) 69 | } 70 | 71 | func addIcon(out *coff.Coff, fname string, newid func() uint16) (io.Closer, error) { 72 | f, err := os.Open(fname) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | icons, err := ico.DecodeHeaders(f) 78 | if err != nil { 79 | f.Close() 80 | return nil, err 81 | } 82 | 83 | if len(icons) > 0 { 84 | // RT_ICONs 85 | group := _GRPICONDIR{ICONDIR: ico.ICONDIR{ 86 | Reserved: 0, // magic num. 87 | Type: 1, // magic num. 88 | Count: uint16(len(icons)), 89 | }} 90 | gid := newid() 91 | for _, icon := range icons { 92 | id := newid() 93 | r := io.NewSectionReader(f, int64(icon.ImageOffset), int64(icon.BytesInRes)) 94 | out.AddResource(coff.RT_ICON, id, r) 95 | group.Entries = append(group.Entries, _GRPICONDIRENTRY{icon.IconDirEntryCommon, id}) 96 | } 97 | out.AddResource(coff.RT_GROUP_ICON, gid, group) 98 | // TODO(akavel): reintroduce the Printlns in package main after Embed returns 99 | // fmt.Println("Icon ", fname, " ID: ", id) 100 | } 101 | 102 | return f, nil 103 | } 104 | -------------------------------------------------------------------------------- /module/rsrc/testdata/akavel.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/module/rsrc/testdata/akavel.ico -------------------------------------------------------------------------------- /module/rsrc/testdata/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | True 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /module/rsrc/testdata/syncthing.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/module/rsrc/testdata/syncthing.ico -------------------------------------------------------------------------------- /module/rsrc/testdata/tmp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello world") 7 | } 8 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /pkg/barrow/barrow.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "slices" 12 | "strconv" 13 | "strings" 14 | "time" 15 | "unicode" 16 | ) 17 | 18 | type BarrowCtx struct { 19 | CWD string 20 | Out string 21 | Target string 22 | Arch string 23 | Release string 24 | Destination string 25 | Pack []string // supported: zip, tar, sh, rpm 26 | Compression string 27 | Verbose bool 28 | extraEnv map[string]string 29 | environ []string 30 | // TODO signature 31 | } 32 | 33 | func (b *BarrowCtx) DbgPrint(format string, a ...any) { 34 | if !b.Verbose { 35 | return 36 | } 37 | message := fmt.Sprintf(format, a...) 38 | message = strings.TrimRightFunc(message, unicode.IsSpace) 39 | lines := strings.Split(message, "\n") 40 | for _, line := range lines { 41 | fmt.Fprintf(os.Stderr, "\x1b[38;2;255;215;0m* %s\x1b[0m\n", line) 42 | } 43 | } 44 | 45 | func (b *BarrowCtx) Getenv(key string) string { 46 | if v, ok := b.extraEnv[key]; ok { 47 | return v 48 | } 49 | return os.Getenv(key) 50 | } 51 | 52 | func (b *BarrowCtx) ExpandEnv(s string) string { 53 | return os.Expand(s, b.Getenv) 54 | } 55 | 56 | func (b *BarrowCtx) LookupEnv(key string) (string, bool) { 57 | if v, ok := b.extraEnv[key]; ok { 58 | return v, true 59 | } 60 | return os.LookupEnv(key) 61 | } 62 | 63 | func resolveGoVersion(ctx context.Context) (version string, host string) { 64 | cmd := exec.CommandContext(ctx, "go", "version") 65 | out, err := cmd.Output() 66 | if err != nil { 67 | return 68 | } 69 | line := strings.TrimRightFunc(string(out), unicode.IsSpace) 70 | sv := strings.Split(line, " ") 71 | if len(sv) < 4 { 72 | return 73 | } 74 | version = strings.TrimPrefix(sv[2], "go") 75 | host = sv[3] 76 | return 77 | } 78 | 79 | func isDistSupported(ctx context.Context, target, arch string) bool { 80 | distName := target + "/" + arch 81 | cmd := exec.CommandContext(ctx, "go", "tool", "dist", "list") 82 | stdout, err := cmd.StdoutPipe() 83 | if err != nil { 84 | fmt.Fprintf(os.Stderr, "check dist is supported error: %v\n", err) 85 | return false 86 | } 87 | defer stdout.Close() 88 | if err := cmd.Start(); err != nil { 89 | fmt.Fprintf(os.Stderr, "check dist is supported error: %v\n", err) 90 | return false 91 | } 92 | defer cmd.Wait() 93 | br := bufio.NewScanner(stdout) 94 | for br.Scan() { 95 | line := strings.TrimSpace(br.Text()) 96 | if line == distName { 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | 103 | func (b *BarrowCtx) makeEnv() { 104 | originEnv := os.Environ() 105 | b.environ = make([]string, 0, len(originEnv)) 106 | for _, e := range originEnv { 107 | k, _, ok := strings.Cut(e, "=") 108 | if !ok { 109 | continue 110 | } 111 | if _, ok = b.extraEnv[k]; ok { 112 | continue 113 | } 114 | b.environ = append(b.environ, e) 115 | } 116 | for k, v := range b.extraEnv { 117 | b.environ = append(b.environ, k+"="+v) 118 | } 119 | } 120 | 121 | func (b *BarrowCtx) binaryName(name string) string { 122 | if b.Target == "windows" { 123 | return name + ".exe" 124 | } 125 | return name 126 | } 127 | 128 | func (b *BarrowCtx) Initialize(ctx context.Context) error { 129 | version, host := resolveGoVersion(ctx) 130 | if !isDistSupported(ctx, b.Target, b.Arch) { 131 | fmt.Fprintf(os.Stderr, "golang %s (dist: %s) not support: %s/%s\n", version, host, b.Target, b.Arch) 132 | return errors.New("dist not supported") 133 | } 134 | b.extraEnv = make(map[string]string) 135 | b.extraEnv["BUILD_GOVERSION"] = version 136 | b.extraEnv["BUILD_HOST"] = host 137 | b.extraEnv["GOOS"] = b.Target 138 | b.extraEnv["GOARCH"] = b.Arch 139 | b.extraEnv["BUILD_TARGET"] = b.Target 140 | b.extraEnv["BUILD_ARCH"] = b.Arch 141 | if err := b.resolveGit(ctx); err != nil { 142 | return err 143 | } 144 | if runID, ok := os.LookupEnv("GITHUB_RUN_ID"); ok { 145 | b.extraEnv["BUILD_RELEASE"] = runID 146 | } 147 | if len(b.Release) == 0 { 148 | b.Release = b.Getenv("BUILD_RELEASE") 149 | } 150 | t := time.Now() 151 | b.extraEnv["BUILD_TIME"] = t.Format(time.RFC3339) 152 | b.extraEnv["BUILD_YEAR"] = strconv.Itoa(t.Year()) 153 | b.makeEnv() 154 | return nil 155 | } 156 | 157 | func (b *BarrowCtx) debugEnv() { 158 | lines := make([]string, 0, len(b.extraEnv)) 159 | for k, v := range b.extraEnv { 160 | lines = append(lines, k+"="+v) 161 | } 162 | slices.Sort(lines) 163 | for _, line := range lines { 164 | fmt.Fprintf(os.Stderr, "\x1b[38;2;255;215;0m* env: %s\x1b[0m\n", line) 165 | } 166 | } 167 | 168 | func (b *BarrowCtx) Run(ctx context.Context) error { 169 | p, err := b.LoadPackage(b.CWD) 170 | if err != nil { 171 | fmt.Fprintf(os.Stderr, "parse package metadata error: %v\n", err) 172 | return err 173 | } 174 | b.DbgPrint("Building %s version: %s target: %s arch: %s", p.Name, p.Version, b.Target, b.Arch) 175 | b.extraEnv["BUILD_VERSION"] = p.Version 176 | 177 | if b.Verbose { 178 | b.debugEnv() 179 | } 180 | 181 | for _, item := range p.Include { 182 | if err := b.apply(item); err != nil { 183 | fmt.Fprintf(os.Stderr, "apply item %s error: %v\n", item.Path, err) 184 | return err 185 | } 186 | } 187 | // compile crates 188 | crates := make([]*Crate, 0, len(p.Crates)) 189 | for _, location := range p.Crates { 190 | crate, err := b.compile(ctx, location) 191 | if err != nil { 192 | return err 193 | } 194 | crates = append(crates, crate) 195 | } 196 | if len(b.Pack) == 0 { 197 | return nil 198 | } 199 | for _, pack := range b.Pack { 200 | switch strings.ToLower(pack) { 201 | case "zip": 202 | if err := b.zip(ctx, p, crates); err != nil { 203 | fmt.Fprintf(os.Stderr, "bali create zip package error: %v\n", err) 204 | return err 205 | } 206 | case "rpm": 207 | if err := b.rpm(ctx, p, crates); err != nil { 208 | fmt.Fprintf(os.Stderr, "bali create rpm package error: %v\n", err) 209 | return err 210 | } 211 | case "sh": 212 | if err := b.sh(ctx, p, crates); err != nil { 213 | fmt.Fprintf(os.Stderr, "bali create sh package error: %v\n", err) 214 | return err 215 | } 216 | case "tar": 217 | if err := b.tar(ctx, p, crates); err != nil { 218 | fmt.Fprintf(os.Stderr, "bali create tar package error: %v\n", err) 219 | return err 220 | } 221 | case "deb": 222 | if err := b.deb(ctx, p, crates); err != nil { 223 | fmt.Fprintf(os.Stderr, "bali create deb package error: %v\n", err) 224 | return err 225 | } 226 | case "apk": 227 | if err := b.apk(ctx, p, crates); err != nil { 228 | fmt.Fprintf(os.Stderr, "bali create deb package error: %v\n", err) 229 | return err 230 | } 231 | case "arch": 232 | if err := b.archLinux(ctx, p, crates); err != nil { 233 | fmt.Fprintf(os.Stderr, "bali create deb package error: %v\n", err) 234 | return err 235 | } 236 | default: 237 | fmt.Fprintf(os.Stderr, "unsupported pack format '%s'\n", b.Pack) 238 | return fmt.Errorf("unsupported pack format '%s'", b.Pack) 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func (b *BarrowCtx) makeAlias(from, to string) error { 245 | if !filepath.IsAbs(to) { 246 | to = filepath.Join(b.Out, to) 247 | } 248 | if _, err := os.Lstat(to); err == nil { 249 | _ = os.Remove(to) 250 | } 251 | if err := os.Symlink(from, to); err != nil { 252 | fmt.Fprintf(os.Stderr, "create symlink error: %v\n", err) 253 | return err 254 | } 255 | return nil 256 | } 257 | 258 | func (b *BarrowCtx) compile(ctx context.Context, location string) (*Crate, error) { 259 | crate, err := b.LoadCrate(location) 260 | if err != nil { 261 | return nil, err 262 | } 263 | releaseFn, err := b.MakeResources(crate) 264 | if err != nil { 265 | fmt.Fprintf(os.Stderr, "crate: %s build resources error \x1b[31m%s\x1b[0m\n", crate.Name, err) 266 | return nil, err 267 | } 268 | if releaseFn != nil { 269 | defer releaseFn() // remove it 270 | } 271 | b.DbgPrint("crate: %s\n", crate.Name) 272 | baseName := b.binaryName(crate.Name) 273 | psArgs := make([]string, 0, 8) 274 | psArgs = append(psArgs, "build", "-o", baseName) 275 | for _, flag := range crate.GoFlags { 276 | psArgs = append(psArgs, b.ExpandEnv(flag)) 277 | } 278 | cmd := exec.CommandContext(ctx, "go", psArgs...) 279 | cmd.Dir = crate.cwd 280 | cmd.Stderr = os.Stderr 281 | cmd.Stdout = os.Stdout 282 | cmd.Env = b.environ 283 | stage("compile", "crate: %s version: %s for %s/%s", crate.Name, crate.Version, b.Target, b.Arch) 284 | status("%s", cmdStringsArgs(cmd)) 285 | if err := cmd.Run(); err != nil { 286 | fmt.Fprintf(os.Stderr, "compile %s error \x1b[31m%s\x1b[0m\n", crate.Name, err) 287 | return nil, err 288 | } 289 | crateDestination := filepath.Join(crate.Destination, baseName) 290 | crateFullPath := filepath.Join(b.Out, crateDestination) 291 | _ = os.MkdirAll(filepath.Dir(crateFullPath), 0755) 292 | if err := os.Rename(filepath.Join(crate.cwd, baseName), crateFullPath); err != nil { 293 | fmt.Fprintf(os.Stderr, "move out to dest error: %v\n", err) 294 | return nil, err 295 | } 296 | for _, a := range crate.Alias { 297 | aliasExpend := b.ExpandEnv(b.binaryName(a)) 298 | stage("compile", "Link \x1b[38;02;39;199;173m%s\x1b[0m --> \x1b[38;02;39;199;173m%s\x1b[0m ", filepath.ToSlash(crateDestination), filepath.ToSlash(aliasExpend)) 299 | if err := b.makeAlias(crateFullPath, aliasExpend); err != nil { 300 | return nil, err 301 | } 302 | } 303 | return crate, nil 304 | } 305 | 306 | func (b *BarrowCtx) Cleanup(force bool) error { 307 | p, err := b.LoadPackage(b.CWD) 308 | if err != nil { 309 | fmt.Fprintf(os.Stderr, "parse package metadata error: %v\n", err) 310 | return err 311 | } 312 | for _, item := range p.Include { 313 | if err := b.cleanupItem(item, force); err != nil { 314 | fmt.Fprintf(os.Stderr, "\x1b[31mcleanup %s error: %v\x1b[0m\n", item.Path, err) 315 | } 316 | } 317 | for _, location := range p.Crates { 318 | if err := b.cleanupCrate(location); err != nil { 319 | fmt.Fprintf(os.Stderr, "\x1b[31mcleanup %s error: %v\x1b[0m\n", location, err) 320 | } 321 | } 322 | if err := b.cleanupPackages(); err != nil { 323 | fmt.Fprintf(os.Stderr, "\x1b[31mcleanup packages error: %v\x1b[0m\n", err) 324 | } 325 | return nil 326 | } 327 | -------------------------------------------------------------------------------- /pkg/barrow/barrow_test.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/pelletier/go-toml/v2" 11 | ) 12 | 13 | func TestParsePerm(t *testing.T) { 14 | i, err := strconv.ParseInt("0755", 8, 64) 15 | if err != nil { 16 | return 17 | } 18 | fmt.Fprintf(os.Stderr, "%d %d %d\n", i, 0755, 0o755) 19 | } 20 | 21 | func TestNameInArchive(t *testing.T) { 22 | prefix := "/usr/local" 23 | dest := "bin" 24 | baseName := "bali" 25 | nameInArchive := filepath.Join(prefix, dest, baseName) 26 | fmt.Fprintf(os.Stderr, "%s %s\n", nameInArchive, ToNixPath(nameInArchive)) 27 | } 28 | 29 | func TestEncodePackage(t *testing.T) { 30 | p := &Package{ 31 | Name: "jack", 32 | Include: []*FileItem{ 33 | { 34 | Path: "LICENSE", 35 | Destination: "share", 36 | }, 37 | { 38 | Path: "README.md", 39 | Destination: "/usr/share", 40 | }, 41 | }, 42 | } 43 | if err := toml.NewEncoder(os.Stderr).Encode(p); err != nil { 44 | fmt.Fprintf(os.Stderr, "encode error: %v\n", err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/barrow/cleanup.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | func (b *BarrowCtx) cleanupCrate(location string) error { 10 | crate, err := b.LoadCrate(location) 11 | if err != nil { 12 | return err 13 | } 14 | b.cleanupResources(crate) 15 | destTo := filepath.Join(b.Out, crate.Destination, crate.Name) 16 | if err := os.Remove(destTo); err == nil { 17 | fmt.Fprintf(os.Stderr, "rm: \x1b[33m%s\x1b[0m\n", destTo) 18 | } 19 | destToExe := destTo + ".exe" 20 | if err := os.Remove(destToExe); err == nil { 21 | fmt.Fprintf(os.Stderr, "rm: \x1b[33m%s\x1b[0m\n", destToExe) 22 | } 23 | return nil 24 | } 25 | 26 | func (b *BarrowCtx) cleanupItem(item *FileItem, force bool) error { 27 | saveDir := filepath.Join(b.Out, item.Destination) 28 | _ = os.MkdirAll(saveDir, 0755) 29 | source := filepath.Join(b.CWD, item.Path) 30 | var destTo string 31 | switch { 32 | case len(item.Rename) != 0: 33 | destTo = filepath.Join(saveDir, item.Rename) 34 | default: 35 | destTo = filepath.Join(saveDir, filepath.Base(item.Path)) 36 | } 37 | if si, err := os.Lstat(destTo); err == nil { 38 | o, err := os.Lstat(source) 39 | if err != nil { 40 | return err 41 | } 42 | if si.ModTime().After(o.ModTime()) && !force { 43 | return nil 44 | } 45 | } 46 | _ = os.Remove(destTo) 47 | fmt.Fprintf(os.Stderr, "rm: \x1b[33m%s\x1b[0m\n", destTo) 48 | return nil 49 | } 50 | 51 | func (b *BarrowCtx) cleanupPackages() error { 52 | absDest, err := filepath.Abs(b.Destination) 53 | if err != nil { 54 | return err 55 | } 56 | files, err := filepath.Glob(filepath.Join(absDest, "*")) 57 | if err != nil { 58 | if os.IsNotExist(err) { 59 | return nil 60 | } 61 | return err 62 | } 63 | for _, item := range files { 64 | fmt.Fprintf(os.Stderr, "rm: \x1b[33m%s\x1b[0m\n", item) 65 | _ = os.Remove(item) 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/barrow/crate.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type Crate struct { 10 | Name string `toml:"name"` 11 | Description string `toml:"description,omitempty"` 12 | Destination string `toml:"destination,omitempty"` 13 | GoFlags []string `toml:"goflags,omitempty"` 14 | Version string `toml:"version,omitempty"` 15 | Alias []string `toml:"alias,omitempty"` // with out suffix 16 | cwd string `toml:"-"` 17 | } 18 | 19 | func (b *BarrowCtx) LoadCrate(location string) (*Crate, error) { 20 | cwd := filepath.Join(b.CWD, location) 21 | file := filepath.Join(cwd, "crate.toml") 22 | var e Crate 23 | if err := LoadMetadata(file, &e); err != nil { 24 | return nil, err 25 | } 26 | e.cwd = cwd 27 | if len(e.Name) == 0 { 28 | e.Name = filepath.Base(cwd) 29 | } 30 | if e.Name == "." { 31 | return nil, fmt.Errorf("unable detect crate name. path '%s'", cwd) 32 | } 33 | if len(e.Version) == 0 { 34 | e.Version = b.Getenv("BUILD_VERSION") 35 | } 36 | return &e, nil 37 | } 38 | 39 | type WinResCloser func() 40 | 41 | func (b *BarrowCtx) MakeResources(e *Crate) (WinResCloser, error) { 42 | if b.Target != "windows" { 43 | return nil, nil 44 | } 45 | saveTo := filepath.Join(e.cwd, "windows_"+b.Arch+".syso") 46 | if err := b.makeResources(e, saveTo); err != nil { 47 | _ = os.Remove(saveTo) 48 | return nil, err 49 | } 50 | return func() { 51 | _ = os.Remove(saveTo) 52 | }, nil 53 | } 54 | 55 | func (b *BarrowCtx) cleanupResources(e *Crate) { 56 | files, err := filepath.Glob(filepath.Join(e.cwd, "*.syso")) 57 | if err != nil { 58 | return 59 | } 60 | for _, item := range files { 61 | fmt.Fprintf(os.Stderr, "rm: \x1b[33m%s\x1b[0m\n", item) 62 | _ = os.Remove(item) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/barrow/misc.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "hash" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func nonEmpty(a string, dv string) string { 15 | if len(a) != 0 { 16 | return a 17 | } 18 | return dv 19 | } 20 | 21 | func isSymlink(fi os.FileInfo) bool { 22 | return fi.Mode()&os.ModeSymlink != 0 23 | } 24 | 25 | // nopCloser wrap io.Writer --> io.WriteCloser 26 | type nopCloser struct { 27 | io.Writer 28 | } 29 | 30 | func (w nopCloser) Close() error { 31 | return nil 32 | } 33 | 34 | // ToNixPath, AsExplicitRelativePath, AsRelativePath (MIT License) 35 | // Thanks: https://github.com/goreleaser/nfpm/blob/main/files/files.go 36 | 37 | // ToNixPath converts the given path to a nix-style path. 38 | // 39 | // Windows-style path separators are considered escape 40 | // characters by some libraries, which can cause issues. 41 | func ToNixPath(path string) string { 42 | return filepath.ToSlash(filepath.Clean(path)) 43 | } 44 | 45 | // As relative path converts a path to an explicitly relative path starting with 46 | // a dot (e.g. it converts /foo -> ./foo and foo -> ./foo). 47 | func AsExplicitRelativePath(path string) string { 48 | return "./" + AsRelativePath(path) 49 | } 50 | 51 | // AsRelativePath converts a path to a relative path without a "./" prefix. This 52 | // function leaves trailing slashes to indicate that the path refers to a 53 | // directory, and converts the path to Unix path. 54 | func AsRelativePath(path string) string { 55 | cleanedPath := strings.TrimLeft(ToNixPath(path), "/") 56 | if len(cleanedPath) > 1 && strings.HasSuffix(path, "/") { 57 | return cleanedPath + "/" 58 | } 59 | return cleanedPath 60 | } 61 | 62 | // NormalizeAbsoluteFilePath returns an absolute cleaned path separated by 63 | // slashes. 64 | func NormalizeAbsoluteFilePath(src string) string { 65 | return ToNixPath(filepath.Join("/", src)) 66 | } 67 | 68 | // normalizeFirPath is linke NormalizeAbsoluteFilePath with a trailing slash. 69 | func NormalizeAbsoluteDirPath(path string) string { 70 | return NormalizeAbsoluteFilePath(strings.TrimRight(path, "/")) + "/" 71 | } 72 | 73 | func hashPrint(h hash.Hash, name string) { 74 | fmt.Fprintf(os.Stdout, "\x1b[38;2;0;191;255m%s %s\x1b[0m\n", hex.EncodeToString(h.Sum(nil)), name) 75 | } 76 | 77 | func stage(s string, format string, a ...any) { 78 | fmt.Fprintf(os.Stderr, "[\x1b[38;2;63;247;166m%s\x1b[0m] \x1b[38;02;39;199;173m%s\x1b[0m\n", s, fmt.Sprintf(format, a...)) 79 | } 80 | 81 | func status(format string, a ...any) { 82 | fmt.Fprintf(os.Stderr, "$> \x1b[38;02;245;202;100m%s\x1b[0m\n", fmt.Sprintf(format, a...)) 83 | } 84 | 85 | // func status(format string, a ...any) { 86 | // var style = lipgloss.NewStyle(). 87 | // Bold(true). 88 | // Foreground(lipgloss.Color("#F5CA64")). 89 | // PaddingTop(0). 90 | // PaddingLeft(1) 91 | 92 | // fmt.Println(style.Render(fmt.Sprintf(format, a...))) 93 | // } 94 | 95 | func cmdStringsArgs(c *exec.Cmd) string { 96 | b := new(strings.Builder) 97 | b.WriteString("go") 98 | for _, a := range c.Args[1:] { 99 | b.WriteByte(' ') 100 | b.WriteString(a) 101 | } 102 | return b.String() 103 | } 104 | -------------------------------------------------------------------------------- /pkg/barrow/nfpm.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "io" 7 | "io/fs" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | 12 | "github.com/goreleaser/nfpm/v2" 13 | "github.com/goreleaser/nfpm/v2/apk" 14 | "github.com/goreleaser/nfpm/v2/arch" 15 | "github.com/goreleaser/nfpm/v2/deb" 16 | "github.com/goreleaser/nfpm/v2/files" 17 | ) 18 | 19 | func (b *BarrowCtx) addItem2Nfpm(info *nfpm.Info, item *FileItem, prefix string) error { 20 | itemPath := filepath.Join(b.CWD, item.Path) 21 | var nameInArchive string 22 | switch { 23 | case len(item.Rename) != 0: 24 | nameInArchive = filepath.Join(prefix, item.Destination, item.Rename) 25 | default: 26 | nameInArchive = filepath.Join(prefix, item.Destination, filepath.Base(item.Path)) 27 | } 28 | si, err := os.Stat(itemPath) 29 | if err != nil { 30 | return err 31 | } 32 | mode := si.Mode().Perm() 33 | if len(item.Permissions) != 0 { 34 | if m, err := strconv.ParseInt(item.Permissions, 8, 64); err == nil { 35 | mode = fs.FileMode(m) 36 | } 37 | } 38 | info.Overridables.Contents = append(info.Overridables.Contents, &files.Content{ 39 | Source: itemPath, 40 | Destination: nameInArchive, 41 | FileInfo: &files.ContentFileInfo{ 42 | Owner: "root", 43 | Group: "root", 44 | Mode: mode, 45 | MTime: si.ModTime(), 46 | Size: si.Size(), 47 | }, 48 | }) 49 | return nil 50 | } 51 | 52 | func (b *BarrowCtx) addCrate2Nfpm(info *nfpm.Info, crate *Crate, prefix string) error { 53 | baseName := b.binaryName(crate.Name) 54 | out := filepath.Join(b.Out, crate.Destination, baseName) 55 | si, err := os.Lstat(out) 56 | if err != nil { 57 | return err 58 | } 59 | nameInArchive := filepath.Join(prefix, crate.Destination, baseName) 60 | info.Overridables.Contents = append(info.Overridables.Contents, &files.Content{ 61 | Source: out, 62 | Destination: AsExplicitRelativePath(nameInArchive), 63 | FileInfo: &files.ContentFileInfo{ 64 | Owner: "root", 65 | Group: "root", 66 | Mode: 0o755, 67 | MTime: si.ModTime(), 68 | Size: si.Size(), 69 | }, 70 | }) 71 | for _, a := range crate.Alias { 72 | aliasExpend := filepath.Join(prefix, b.ExpandEnv(b.binaryName(a))) 73 | aliasPath, err := filepath.Rel(filepath.Dir(aliasExpend), filepath.Dir(nameInArchive)) 74 | if err != nil { 75 | return err 76 | } 77 | aliasLink := filepath.Join(aliasPath, filepath.Base(nameInArchive)) 78 | info.Overridables.Contents = append(info.Overridables.Contents, &files.Content{ 79 | Source: AsExplicitRelativePath(aliasLink), 80 | Destination: AsExplicitRelativePath(aliasExpend), 81 | Type: files.TypeSymlink, 82 | }) 83 | } 84 | return nil 85 | } 86 | 87 | func (b *BarrowCtx) deb(ctx context.Context, p *Package, crates []*Crate) error { 88 | select { 89 | case <-ctx.Done(): 90 | return ctx.Err() 91 | default: 92 | } 93 | if len(p.Maintainer) == 0 { 94 | p.Maintainer = "Unset Maintainer " 95 | } 96 | info := nfpm.WithDefaults(&nfpm.Info{ 97 | Name: p.Name, 98 | Platform: b.Target, 99 | Arch: b.Arch, 100 | Description: p.Description, 101 | Version: p.Version, 102 | Release: b.Release, 103 | Maintainer: p.Maintainer, 104 | Vendor: p.Vendor, 105 | Homepage: p.Homepage, 106 | License: p.License, 107 | }) 108 | for _, item := range p.Include { 109 | if err := b.addItem2Nfpm(info, item, p.Prefix); err != nil { 110 | return err 111 | } 112 | } 113 | for _, crate := range crates { 114 | if err := b.addCrate2Nfpm(info, crate, p.Prefix); err != nil { 115 | return err 116 | } 117 | } 118 | debPackageName := deb.Default.ConventionalFileName(info) 119 | var debPath string 120 | if filepath.IsAbs(b.Destination) { 121 | debPath = filepath.Join(b.Destination, debPackageName) 122 | } else { 123 | debPath = filepath.Join(b.CWD, b.Destination, debPackageName) 124 | } 125 | _ = os.MkdirAll(filepath.Dir(debPath), 0755) 126 | fd, err := os.Create(debPath) 127 | if err != nil { 128 | return err 129 | } 130 | defer fd.Close() 131 | h := sha256.New() 132 | w := io.MultiWriter(fd, h) 133 | if err := deb.Default.Package(info, w); err != nil { 134 | return err 135 | } 136 | hashPrint(h, debPackageName) 137 | return nil 138 | } 139 | 140 | func (b *BarrowCtx) apk(ctx context.Context, p *Package, crates []*Crate) error { 141 | select { 142 | case <-ctx.Done(): 143 | return ctx.Err() 144 | default: 145 | } 146 | if len(p.Maintainer) == 0 { 147 | p.Maintainer = "Unset Maintainer " 148 | } 149 | info := nfpm.WithDefaults(&nfpm.Info{ 150 | Name: p.Name, 151 | Platform: b.Target, 152 | Arch: b.Arch, 153 | Description: p.Description, 154 | Version: p.Version, 155 | Release: b.Release, 156 | Maintainer: p.Maintainer, 157 | Vendor: p.Vendor, 158 | Homepage: p.Homepage, 159 | License: p.License, 160 | }) 161 | for _, item := range p.Include { 162 | if err := b.addItem2Nfpm(info, item, p.Prefix); err != nil { 163 | return err 164 | } 165 | } 166 | for _, crate := range crates { 167 | if err := b.addCrate2Nfpm(info, crate, p.Prefix); err != nil { 168 | return err 169 | } 170 | } 171 | apkPackageName := apk.Default.ConventionalFileName(info) 172 | var apkPath string 173 | if filepath.IsAbs(b.Destination) { 174 | apkPath = filepath.Join(b.Destination, apkPackageName) 175 | } else { 176 | apkPath = filepath.Join(b.CWD, b.Destination, apkPackageName) 177 | } 178 | _ = os.MkdirAll(filepath.Dir(apkPath), 0755) 179 | fd, err := os.Create(apkPath) 180 | if err != nil { 181 | return err 182 | } 183 | defer fd.Close() 184 | h := sha256.New() 185 | w := io.MultiWriter(fd, h) 186 | if err := apk.Default.Package(info, w); err != nil { 187 | return err 188 | } 189 | hashPrint(h, apkPackageName) 190 | return nil 191 | } 192 | 193 | func (b *BarrowCtx) archLinux(ctx context.Context, p *Package, crates []*Crate) error { 194 | select { 195 | case <-ctx.Done(): 196 | return ctx.Err() 197 | default: 198 | } 199 | if len(p.Maintainer) == 0 { 200 | p.Maintainer = "Unset Maintainer " 201 | } 202 | info := nfpm.WithDefaults(&nfpm.Info{ 203 | Name: p.Name, 204 | Platform: b.Target, 205 | Arch: b.Arch, 206 | Description: p.Description, 207 | Version: p.Version, 208 | Release: b.Release, 209 | Maintainer: p.Maintainer, 210 | Vendor: p.Vendor, 211 | Homepage: p.Homepage, 212 | License: p.License, 213 | }) 214 | for _, item := range p.Include { 215 | if err := b.addItem2Nfpm(info, item, p.Prefix); err != nil { 216 | return err 217 | } 218 | } 219 | for _, crate := range crates { 220 | if err := b.addCrate2Nfpm(info, crate, p.Prefix); err != nil { 221 | return err 222 | } 223 | } 224 | archLinuxPackageName := arch.Default.ConventionalFileName(info) 225 | var archLinuxPath string 226 | if filepath.IsAbs(b.Destination) { 227 | archLinuxPath = filepath.Join(b.Destination, archLinuxPackageName) 228 | } else { 229 | archLinuxPath = filepath.Join(b.CWD, b.Destination, archLinuxPackageName) 230 | } 231 | _ = os.MkdirAll(filepath.Dir(archLinuxPath), 0755) 232 | fd, err := os.Create(archLinuxPath) 233 | if err != nil { 234 | return err 235 | } 236 | defer fd.Close() 237 | h := sha256.New() 238 | w := io.MultiWriter(fd, h) 239 | if err := arch.Default.Package(info, w); err != nil { 240 | return err 241 | } 242 | hashPrint(h, archLinuxPackageName) 243 | return nil 244 | } 245 | -------------------------------------------------------------------------------- /pkg/barrow/package.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/fs" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | 11 | "github.com/pelletier/go-toml/v2" 12 | ) 13 | 14 | type FileItem struct { 15 | Path string `toml:"path"` 16 | Destination string `toml:"destination"` 17 | Rename string `toml:"rename,omitempty"` // when rename is no empty: rename file to name 18 | Permissions string `toml:"permissions,omitempty"` // 0755 0644 19 | } 20 | 21 | type Package struct { 22 | Name string `toml:"name"` 23 | PackageName string `toml:"package-name,omitempty"` 24 | Summary string `toml:"summary,omitempty"` // Is a short description of the software 25 | Description string `toml:"description,omitempty"` // description is a longer piece of software information than Summary, consisting of one or more paragraphs 26 | Version string `toml:"version,omitempty"` 27 | Authors []string `toml:"authors,omitempty"` 28 | Vendor string `toml:"vendor,omitempty"` 29 | Maintainer string `toml:"maintainer,omitempty"` 30 | Homepage string `toml:"homepage,omitempty"` 31 | Packager string `toml:"packager,omitempty"` // BALI_RPM_PACKAGER 32 | Group string `toml:"group,omitempty"` 33 | License string `toml:"license,omitempty"` 34 | LicenseFile string `toml:"license-file,omitempty"` 35 | Prefix string `toml:"prefix,omitempty"` // install prefix: rpm required 36 | Crates []string `toml:"crates,omitempty"` 37 | Include []*FileItem `toml:"include,omitempty"` 38 | } 39 | 40 | func LoadMetadata(file string, v any) error { 41 | fd, err := os.Open(file) 42 | if err != nil { 43 | return err 44 | } 45 | defer fd.Close() 46 | if err := toml.NewDecoder(fd).Decode(v); err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func (b *BarrowCtx) LoadPackage(cwd string) (*Package, error) { 53 | file := filepath.Join(cwd, "bali.toml") 54 | var p Package 55 | if err := LoadMetadata(file, &p); err != nil { 56 | return nil, err 57 | } 58 | if packageName, ok := os.LookupEnv("PACKAGE_NAME"); ok { 59 | p.PackageName = packageName // overwrite 60 | } 61 | // OS BUILD_VERSION 62 | if version, ok := os.LookupEnv("BUILD_VERSION"); ok { 63 | p.Version = version 64 | } 65 | return &p, nil 66 | } 67 | 68 | func copyTo(src, dest string, newPerm string) error { 69 | st, err := os.Stat(src) 70 | if err != nil { 71 | return err 72 | } 73 | if !st.Mode().IsRegular() { 74 | // cannot copy non-regular files (e.g., directories, 75 | // symlinks, devices, etc.) 76 | return fmt.Errorf("copyTo: non-regular source file %s (%q)", st.Name(), st.Mode().String()) 77 | } 78 | dst, err := os.Stat(dest) 79 | if err != nil { 80 | if !os.IsNotExist(err) { 81 | return err 82 | } 83 | } else { 84 | if !dst.Mode().IsRegular() { 85 | return fmt.Errorf("copyTo: non-regular destination file %s (%q)", dst.Name(), dst.Mode().String()) 86 | } 87 | if os.SameFile(st, dst) { 88 | return nil 89 | } 90 | } 91 | in, err := os.Open(src) 92 | if err != nil { 93 | return err 94 | } 95 | defer in.Close() 96 | perm := st.Mode().Perm() 97 | if len(newPerm) != 0 { 98 | if m, err := strconv.ParseInt(newPerm, 8, 64); err == nil { 99 | perm = fs.FileMode(m) 100 | } 101 | } 102 | out, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm) 103 | if err != nil { 104 | return err 105 | } 106 | defer out.Close() 107 | if _, err := io.Copy(out, in); err != nil { 108 | return err 109 | } 110 | return out.Sync() 111 | } 112 | 113 | func (b *BarrowCtx) apply(item *FileItem) error { 114 | saveDir := filepath.Join(b.Out, item.Destination) 115 | _ = os.MkdirAll(saveDir, 0755) 116 | source := filepath.Join(b.CWD, item.Path) 117 | var saveTo string 118 | switch { 119 | case len(item.Rename) != 0: 120 | saveTo = filepath.Join(saveDir, item.Rename) 121 | default: 122 | saveTo = filepath.Join(saveDir, filepath.Base(item.Path)) 123 | } 124 | if si, err := os.Stat(saveTo); err == nil { 125 | o, err := os.Stat(source) 126 | if err != nil { 127 | return err 128 | } 129 | if si.ModTime().After(o.ModTime()) { 130 | return nil 131 | } 132 | } 133 | if err := copyTo(source, saveTo, item.Permissions); err != nil { 134 | fmt.Fprintf(os.Stderr, "install %s error: %v\n", item.Path, err) 135 | return err 136 | } 137 | if len(item.Rename) != 0 { 138 | stage("install", "\x1b[38;02;39;199;173m%s\x1b[0m --> \x1b[38;02;39;199;173m%s\x1b[0m done", item.Path, filepath.Join(item.Destination, item.Rename)) 139 | return nil 140 | } 141 | stage("install", "\x1b[38;02;39;199;173m%s\x1b[0m --> \x1b[38;02;39;199;173m%s\x1b[0m done", item.Path, filepath.Base(item.Path)) 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /pkg/barrow/resources/template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | cat </dev/null 19 | if [ $? -eq 4 ]; then 20 | ARGS=$(getopt -a -o p:hv --long prefix:,version,help -- "$@") 21 | eval set -- "${ARGS}" 22 | while :; do 23 | case $1 in 24 | -p | --prefix) 25 | install_prefix=$2 26 | install_prefix=$(fix_slashes "${install_prefix}") 27 | shift 28 | ;; 29 | -h | --help) 30 | usage 31 | ;; 32 | -v | --version) 33 | echo "1.0.0" 34 | exit 0 35 | ;; 36 | --) 37 | shift 38 | break 39 | ;; 40 | *) 41 | echo "Internal error!" 42 | exit 1 43 | ;; 44 | esac 45 | shift 46 | done 47 | else 48 | for a in "$@"; do 49 | if echo "$a" | grep "^--prefix=" >/dev/null 2>/dev/null; then 50 | install_prefix="${a/--prefix=\///}" 51 | install_prefix=$(fix_slashes "${install_prefix}") 52 | continue 53 | fi 54 | if echo "$a" | grep "^--version" >/dev/null 2>/dev/null; then 55 | echo "1.0.0" 56 | exit 0 57 | fi 58 | if echo "$a" | grep "^--prefix" >/dev/null 2>/dev/null; then 59 | echo -e "error: \x1b[31m--prefix /path/to/prefix\x1b[0m is not support, switch to: \x1b[31m--prefix=/path/to/prefix\x1b[0m" 60 | exit 1 61 | fi 62 | if echo "$a" | grep "^--help" >/dev/null 2>/dev/null; then 63 | usage 64 | fi 65 | done 66 | fi 67 | 68 | echo "This is a self-extracting archive." 69 | prefix=$(pwd) 70 | if [[ "x${install_prefix}" != "x" ]]; then 71 | prefix="${install_prefix}" 72 | fi 73 | package=$(basename "$0") 74 | echo -e "The ${package} will be extracted to: \\x1b[32m${prefix}\\x1b[0m" 75 | if [ ! -d "${prefix}" ]; then 76 | mkdir -p "${prefix}" || exit 1 77 | fi 78 | echo 79 | echo "Using traget directory: ${prefix}" 80 | echo "Extracting, please wait..." 81 | echo "" 82 | ARCHIVE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0") 83 | tail "-n+$ARCHIVE" "$0" | tar xzvm -C "$prefix" >/dev/null 2>&1 3>&1 84 | if [[ -f "${prefix}/post-install.sh" ]]; then 85 | chmod +x "${prefix}/post-install.sh" 86 | echo -e "\\x1b[33mrun ${prefix}/post-install.sh\\x1b[0m" 87 | bash "${prefix}/post-install.sh" 88 | fi 89 | exit 0 90 | #This line must be the last line of the file 91 | # shellcheck disable=SC2317 92 | __ARCHIVE_BELOW__ 93 | -------------------------------------------------------------------------------- /pkg/barrow/rpm.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "fmt" 7 | "io" 8 | "io/fs" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/google/rpmpack" 16 | ) 17 | 18 | // https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/rpm_packaging_guide/working-with-spec-files 19 | 20 | const ( 21 | // Symbolic link 22 | tagLink = 0o120000 23 | ) 24 | 25 | func (b *BarrowCtx) addItem2RPM(r *rpmpack.RPM, item *FileItem, prefix string) error { 26 | itemPath := filepath.Join(b.CWD, item.Path) 27 | var nameInArchive string 28 | switch { 29 | case len(item.Rename) != 0: 30 | nameInArchive = filepath.Join(prefix, item.Destination, item.Rename) 31 | default: 32 | nameInArchive = filepath.Join(prefix, item.Destination, filepath.Base(item.Path)) 33 | } 34 | si, err := os.Lstat(itemPath) 35 | if err != nil { 36 | return err 37 | } 38 | if isSymlink(si) { 39 | linkTarget, err := os.Readlink(itemPath) 40 | if err != nil { 41 | return fmt.Errorf("add %s to zip error: %w", nameInArchive, err) 42 | } 43 | r.AddFile(rpmpack.RPMFile{ 44 | Name: ToNixPath(nameInArchive), 45 | Body: []byte(ToNixPath(linkTarget)), 46 | Mode: tagLink, 47 | Group: "root", 48 | Owner: "root", 49 | MTime: uint32(si.ModTime().Unix()), 50 | }) 51 | return nil 52 | } 53 | mode := si.Mode().Perm() 54 | if len(item.Permissions) != 0 { 55 | if m, err := strconv.ParseInt(item.Permissions, 8, 64); err == nil { 56 | mode = fs.FileMode(m) 57 | } 58 | } 59 | fd, err := os.Open(itemPath) 60 | if err != nil { 61 | return err 62 | } 63 | defer fd.Close() 64 | payload, err := io.ReadAll(fd) 65 | if err != nil { 66 | return err 67 | } 68 | r.AddFile(rpmpack.RPMFile{ 69 | Name: ToNixPath(nameInArchive), 70 | Body: payload, 71 | Mode: uint(mode), 72 | Group: "root", 73 | Owner: "root", 74 | MTime: uint32(si.ModTime().Unix()), 75 | }) 76 | return nil 77 | } 78 | 79 | func (b *BarrowCtx) addCrate2RPM(r *rpmpack.RPM, crate *Crate, prefix string) error { 80 | baseName := b.binaryName(crate.Name) 81 | out := filepath.Join(b.Out, crate.Destination, baseName) 82 | fd, err := os.Open(out) 83 | if err != nil { 84 | return err 85 | } 86 | defer fd.Close() 87 | si, err := fd.Stat() 88 | if err != nil { 89 | return err 90 | } 91 | payload, err := io.ReadAll(fd) 92 | if err != nil { 93 | return err 94 | } 95 | nameInArchive := filepath.Join(prefix, crate.Destination, baseName) 96 | r.AddFile(rpmpack.RPMFile{ 97 | Name: ToNixPath(nameInArchive), 98 | Body: payload, 99 | Mode: 0755, 100 | Group: "root", 101 | Owner: "root", 102 | MTime: uint32(si.ModTime().Unix()), 103 | }) 104 | return nil 105 | } 106 | 107 | var ( 108 | rpmSupportedCompressor = map[string]bool{ 109 | "": true, 110 | "gzip": true, 111 | "zstd": true, 112 | "lzma": true, 113 | "xz": true, 114 | } 115 | // https://docs.fedoraproject.org/ro/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch01s03.html 116 | // nolint: gochecknoglobals 117 | archToRPM = map[string]string{ 118 | "all": "noarch", 119 | "amd64": "x86_64", 120 | "386": "i386", 121 | "arm64": "aarch64", 122 | "arm5": "armv5tel", 123 | "arm6": "armv6hl", 124 | "arm7": "armv7hl", 125 | "mips64le": "mips64el", 126 | "mipsle": "mipsel", 127 | "mips": "mips", 128 | // TODO: other arches 129 | } 130 | ) 131 | 132 | func rpmArchGuard(arch string) string { 133 | if a, ok := archToRPM[arch]; ok { 134 | return a 135 | } 136 | return arch 137 | } 138 | 139 | func (b *BarrowCtx) rpm(ctx context.Context, p *Package, crates []*Crate) error { 140 | select { 141 | case <-ctx.Done(): 142 | return ctx.Err() 143 | default: 144 | } 145 | if !rpmSupportedCompressor[b.Compression] { 146 | return fmt.Errorf("unsupported compressor '%s'", b.Compression) 147 | } 148 | r, err := rpmpack.NewRPM(rpmpack.RPMMetaData{ 149 | Name: nonEmpty(p.PackageName, p.Name), 150 | Summary: nonEmpty(p.Summary, strings.Split(p.Description, "\n")[0]), 151 | Description: p.Description, 152 | Version: p.Version, 153 | Release: nonEmpty(b.Release, "1"), 154 | Arch: rpmArchGuard(b.Arch), 155 | Vendor: p.Vendor, 156 | URL: p.Homepage, 157 | Packager: p.Packager, 158 | Group: p.Group, 159 | Licence: p.License, 160 | BuildHost: b.Getenv("BUILD_HOST"), 161 | Compressor: b.Compression, 162 | BuildTime: time.Now(), 163 | }) 164 | if err != nil { 165 | return err 166 | } 167 | for _, item := range p.Include { 168 | if err := b.addItem2RPM(r, item, p.Prefix); err != nil { 169 | return err 170 | } 171 | } 172 | for _, crate := range crates { 173 | if err := b.addCrate2RPM(r, crate, p.Prefix); err != nil { 174 | return err 175 | } 176 | } 177 | rpmPackageName := fmt.Sprintf("%s-%s-%s.%s.rpm", r.Name, r.Version, r.Release, r.Arch) 178 | var rpmPath string 179 | if filepath.IsAbs(b.Destination) { 180 | rpmPath = filepath.Join(b.Destination, rpmPackageName) 181 | } else { 182 | rpmPath = filepath.Join(b.CWD, b.Destination, rpmPackageName) 183 | } 184 | _ = os.MkdirAll(filepath.Dir(rpmPath), 0755) 185 | fd, err := os.Create(rpmPath) 186 | if err != nil { 187 | return err 188 | } 189 | defer fd.Close() 190 | h := sha256.New() 191 | w := io.MultiWriter(fd, h) 192 | if err := r.Write(w); err != nil { 193 | return err 194 | } 195 | hashPrint(h, rpmPackageName) 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /pkg/barrow/tar.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "context" 7 | "crypto/sha256" 8 | "embed" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | 15 | "github.com/andybalholm/brotli" 16 | "github.com/dsnet/compress/bzip2" 17 | "github.com/klauspost/compress/zstd" 18 | "github.com/ulikunitz/xz" 19 | ) 20 | 21 | func (b *BarrowCtx) addItem2Tar(z *tar.Writer, item *FileItem, prefix string) error { 22 | itemPath := filepath.Join(b.CWD, item.Path) 23 | si, err := os.Stat(itemPath) 24 | if err != nil { 25 | return err 26 | } 27 | var nameInArchive string 28 | switch { 29 | case len(item.Rename) != 0: 30 | nameInArchive = filepath.Join(prefix, item.Destination, item.Rename) 31 | default: 32 | nameInArchive = filepath.Join(prefix, item.Destination, filepath.Base(item.Path)) 33 | } 34 | hdr, err := tar.FileInfoHeader(si, "") 35 | if err != nil { 36 | return err 37 | } 38 | if len(item.Permissions) != 0 { 39 | if m, err := strconv.ParseInt(item.Permissions, 8, 64); err == nil { 40 | hdr.Mode = m 41 | } 42 | } 43 | hdr.Name = AsExplicitRelativePath(nameInArchive) 44 | if err = z.WriteHeader(hdr); err != nil { 45 | return fmt.Errorf("write tar header error: %w", err) 46 | } 47 | if si.IsDir() { 48 | return nil 49 | } 50 | if hdr.Typeflag != tar.TypeReg { 51 | return nil 52 | } 53 | fd, err := os.Open(itemPath) 54 | if err != nil { 55 | return err 56 | } 57 | defer fd.Close() 58 | if _, err := io.Copy(z, fd); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | func (b *BarrowCtx) addCrate2Tar(z *tar.Writer, crate *Crate, prefix string) error { 65 | baseName := b.binaryName(crate.Name) 66 | out := filepath.Join(b.Out, crate.Destination, baseName) 67 | si, err := os.Lstat(out) 68 | if err != nil { 69 | return err 70 | } 71 | nameInArchive := filepath.Join(prefix, crate.Destination, baseName) 72 | hdr, err := tar.FileInfoHeader(si, "") 73 | if err != nil { 74 | return err 75 | } 76 | hdr.Name = AsExplicitRelativePath(nameInArchive) 77 | hdr.Mode = 0755 78 | if err = z.WriteHeader(hdr); err != nil { 79 | return fmt.Errorf("write tar header error: %w", err) 80 | } 81 | fd, err := os.Open(out) 82 | if err != nil { 83 | return err 84 | } 85 | defer fd.Close() 86 | if _, err := io.Copy(z, fd); err != nil { 87 | return err 88 | } 89 | for _, a := range crate.Alias { 90 | aliasExpend := filepath.Join(prefix, b.ExpandEnv(b.binaryName(a))) 91 | aliasPath, err := filepath.Rel(filepath.Dir(aliasExpend), filepath.Dir(nameInArchive)) 92 | if err != nil { 93 | return err 94 | } 95 | aliasLink := filepath.Join(aliasPath, filepath.Base(nameInArchive)) 96 | ah := &tar.Header{ 97 | Name: AsExplicitRelativePath(aliasExpend), 98 | Linkname: AsExplicitRelativePath(aliasLink), 99 | Typeflag: tar.TypeSymlink, 100 | Format: tar.FormatGNU, 101 | ModTime: si.ModTime(), 102 | } 103 | if err = z.WriteHeader(ah); err != nil { 104 | return fmt.Errorf("write tar header error: %w", err) 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func (b *BarrowCtx) tarInternal(p *Package, crates []*Crate, prefix string, w io.Writer) error { 111 | z := tar.NewWriter(w) 112 | for _, item := range p.Include { 113 | if err := b.addItem2Tar(z, item, prefix); err != nil { 114 | _ = z.Close() 115 | return err 116 | } 117 | } 118 | for _, crate := range crates { 119 | if err := b.addCrate2Tar(z, crate, prefix); err != nil { 120 | _ = z.Close() 121 | return err 122 | } 123 | } 124 | return z.Close() 125 | } 126 | 127 | //go:embed resources/template.sh 128 | var resources embed.FS 129 | 130 | func (b *BarrowCtx) sh(ctx context.Context, p *Package, crates []*Crate) error { 131 | select { 132 | case <-ctx.Done(): 133 | return ctx.Err() 134 | default: 135 | } 136 | newCompressor, _, err := tarCompressor(b.Compression) 137 | if err != nil { 138 | return err 139 | } 140 | tarFileName := fmt.Sprintf("%s-%s-%s-%s.sh", p.Name, p.Version, b.Target, b.Arch) 141 | var tarPath string 142 | if filepath.IsAbs(b.Destination) { 143 | tarPath = filepath.Join(b.Destination, tarFileName) 144 | } else { 145 | tarPath = filepath.Join(b.CWD, b.Destination, tarFileName) 146 | } 147 | _ = os.MkdirAll(filepath.Dir(tarPath), 0755) 148 | fd, err := os.OpenFile(tarPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) 149 | if err != nil { 150 | return err 151 | } 152 | h := sha256.New() 153 | w := io.MultiWriter(fd, h) 154 | rfd, err := resources.Open("resources/template.sh") 155 | if err != nil { 156 | return err 157 | } 158 | if _, err := fd.ReadFrom(rfd); err != nil { 159 | _ = fd.Close() 160 | return err 161 | } 162 | cw, err := newCompressor(w) 163 | if err != nil { 164 | _ = fd.Close() 165 | return err 166 | } 167 | if err := b.tarInternal(p, crates, "", cw); err != nil { 168 | fmt.Fprintf(os.Stderr, "zip errpr: %d\n", err) 169 | _ = cw.Close() 170 | _ = fd.Close() 171 | _ = os.RemoveAll(tarPath) 172 | return err 173 | } 174 | if err := cw.Close(); err != nil { 175 | _ = fd.Close() 176 | _ = os.RemoveAll(tarPath) 177 | return err 178 | } 179 | hashPrint(h, tarFileName) 180 | return nil 181 | } 182 | 183 | type FnCompressor func(w io.Writer) (io.WriteCloser, error) 184 | 185 | func tarCompressor(method string) (FnCompressor, string, error) { 186 | switch method { 187 | case "zstd": 188 | return func(w io.Writer) (io.WriteCloser, error) { 189 | return zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) 190 | }, ".tar.zst", nil 191 | case "xz": 192 | return func(w io.Writer) (io.WriteCloser, error) { 193 | return xz.NewWriter(w) 194 | }, ".tar.xz", nil 195 | case "bzip2": 196 | return func(w io.Writer) (io.WriteCloser, error) { 197 | return bzip2.NewWriter(w, nil) 198 | }, ".tar.bz2", nil 199 | case "brotli": 200 | return func(w io.Writer) (io.WriteCloser, error) { 201 | return brotli.NewWriter(w), nil 202 | }, ".tar.br", nil 203 | case "", "gzip": 204 | return func(w io.Writer) (io.WriteCloser, error) { 205 | return gzip.NewWriter(w), nil 206 | }, ".tar.gz", nil 207 | case "none": 208 | return func(w io.Writer) (io.WriteCloser, error) { 209 | return &nopCloser{Writer: w}, nil 210 | }, ".tar", nil 211 | default: 212 | return nil, "", fmt.Errorf("unsupported tar compress method '%s'", method) 213 | } 214 | } 215 | 216 | func (b *BarrowCtx) tar(ctx context.Context, p *Package, crates []*Crate) error { 217 | select { 218 | case <-ctx.Done(): 219 | return ctx.Err() 220 | default: 221 | } 222 | 223 | newCompressor, suffix, err := tarCompressor(b.Compression) 224 | if err != nil { 225 | return err 226 | } 227 | tarPrefix := fmt.Sprintf("%s-%s-%s-%s", p.Name, p.Version, b.Target, b.Arch) 228 | tarFileName := tarPrefix + suffix 229 | var tarPath string 230 | if filepath.IsAbs(b.Destination) { 231 | tarPath = filepath.Join(b.Destination, tarFileName) 232 | } else { 233 | tarPath = filepath.Join(b.CWD, b.Destination, tarFileName) 234 | } 235 | _ = os.MkdirAll(filepath.Dir(tarPath), 0755) 236 | fd, err := os.Create(tarPath) 237 | if err != nil { 238 | return err 239 | } 240 | h := sha256.New() 241 | cw, err := newCompressor(io.MultiWriter(fd, h)) 242 | if err != nil { 243 | _ = fd.Close() 244 | return err 245 | } 246 | if err := b.tarInternal(p, crates, tarPrefix, cw); err != nil { 247 | fmt.Fprintf(os.Stderr, "zip errpr: %d\n", err) 248 | _ = cw.Close() 249 | _ = fd.Close() 250 | _ = os.RemoveAll(tarPath) 251 | return err 252 | } 253 | if err := cw.Close(); err != nil { 254 | _ = fd.Close() 255 | _ = os.RemoveAll(tarPath) 256 | return err 257 | } 258 | hashPrint(h, tarFileName) 259 | return nil 260 | } 261 | -------------------------------------------------------------------------------- /pkg/barrow/vcs.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | func (b *BarrowCtx) resolveHEAD(ctx context.Context) (string, error) { 11 | cmd := exec.CommandContext(ctx, "git", "rev-parse", "HEAD") 12 | cmd.Dir = b.CWD 13 | if out, err := cmd.Output(); err == nil { 14 | return strings.TrimSpace(string(out)), nil 15 | } 16 | if s, ok := os.LookupEnv("GITHUB_SHA"); ok { 17 | return s, nil 18 | } 19 | return "", os.ErrNotExist 20 | } 21 | 22 | func (b *BarrowCtx) resolveReferenceName(ctx context.Context) (string, error) { 23 | cmd := exec.CommandContext(ctx, "git", "symbolic-ref", "HEAD") 24 | cmd.Dir = b.CWD 25 | if out, err := cmd.Output(); err == nil { 26 | return strings.TrimSpace(string(out)), nil 27 | } 28 | if refname, ok := os.LookupEnv("GITHUB_REF"); ok { 29 | // Github support this 30 | return refname, nil 31 | } 32 | if tagName, ok := os.LookupEnv("GIT_BUILD_REF"); ok { 33 | // CODING support this 34 | return tagName, nil 35 | } 36 | return "", os.ErrNotExist 37 | } 38 | 39 | // git describe --tags --dirty 40 | func (b *BarrowCtx) resolveDirtyTagName(ctx context.Context) (string, error) { 41 | cmd := exec.CommandContext(ctx, "git", "describe", "--tags", "--dirty") 42 | cmd.Dir = b.CWD 43 | out, err := cmd.Output() 44 | if err != nil { 45 | // continue 46 | return "", err 47 | } 48 | return strings.TrimSpace(string(out)), nil 49 | } 50 | 51 | func (b *BarrowCtx) resolveGit(ctx context.Context) error { 52 | if HEAD, err := b.resolveHEAD(ctx); err == nil { 53 | b.extraEnv["BUILD_COMMIT"] = HEAD 54 | } 55 | if n, err := b.resolveDirtyTagName(ctx); err == nil { 56 | b.extraEnv["BUILD_DIRTY_TAGNAME"] = n 57 | } 58 | if n, err := b.resolveReferenceName(ctx); err == nil { 59 | if branchName, ok := strings.CutPrefix(n, "refs/heads/"); ok { 60 | b.extraEnv["BUILD_BRANCH"] = branchName 61 | } 62 | b.extraEnv["BUILD_REFNAME"] = n 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/barrow/version.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/balibuild/bali/v3/module/goversioninfo" 8 | ) 9 | 10 | func (b *BarrowCtx) makeResources(e *Crate, saveTo string) error { 11 | var vi goversioninfo.VersionInfo 12 | if err := LoadMetadata(filepath.Join(e.cwd, "winres.toml"), &vi); err != nil && !os.IsNotExist(err) { 13 | return err 14 | } 15 | if len(vi.StringFileInfo.FileVersion) == 0 { 16 | vi.StringFileInfo.FileVersion = e.Version 17 | } 18 | vi.FixedFileInfo.FileVersion.Overwrite(e.Version) 19 | if len(vi.StringFileInfo.ProductVersion) == 0 { 20 | vi.StringFileInfo.ProductVersion = e.Version 21 | } 22 | vi.FixedFileInfo.ProductVersion.Overwrite(e.Version) 23 | if len(vi.StringFileInfo.ProductName) == 0 { 24 | vi.StringFileInfo.ProductName = e.Name 25 | } 26 | if len(vi.StringFileInfo.InternalName) == 0 { 27 | vi.StringFileInfo.InternalName = b.binaryName(e.Name) 28 | } 29 | if len(vi.StringFileInfo.FileDescription) == 0 { 30 | vi.StringFileInfo.FileDescription = e.Description 31 | } 32 | if len(vi.FixedFileInfo.FileFlagsMask) == 0 { 33 | vi.FixedFileInfo.FileFlagsMask = "3f" 34 | } 35 | if len(vi.FixedFileInfo.FileFlags) == 0 { 36 | vi.FixedFileInfo.FileFlagsMask = "00" 37 | } 38 | if len(vi.FixedFileInfo.FileOS) == 0 { 39 | // HEX 40 | vi.FixedFileInfo.FileOS = "40004" 41 | } 42 | if len(vi.FixedFileInfo.FileType) == 0 { 43 | vi.FixedFileInfo.FileType = "01" 44 | } 45 | if len(vi.FixedFileInfo.FileSubType) == 0 { 46 | vi.FixedFileInfo.FileSubType = "00" 47 | } 48 | if vi.VarFileInfo.Translation.LangID == 0 { 49 | vi.VarFileInfo.Translation.LangID = goversioninfo.LngUSEnglish 50 | } 51 | if vi.VarFileInfo.Translation.CharsetID == 0 { 52 | vi.VarFileInfo.Translation.CharsetID = goversioninfo.CsUnicode 53 | } 54 | vi.Build() 55 | vi.Walk() 56 | return vi.WriteSyso(e.cwd, saveTo, b.Arch) 57 | } 58 | -------------------------------------------------------------------------------- /pkg/barrow/zip.go: -------------------------------------------------------------------------------- 1 | package barrow 2 | 3 | import ( 4 | "archive/zip" 5 | "context" 6 | "crypto/sha256" 7 | "fmt" 8 | "hash" 9 | "io" 10 | "io/fs" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | 15 | "github.com/dsnet/compress/bzip2" 16 | "github.com/klauspost/compress/zstd" 17 | "github.com/ulikunitz/xz" 18 | ) 19 | 20 | const ( 21 | Store uint16 = 0 // no compression 22 | Deflate uint16 = 8 // DEFLATE compressed 23 | BZIP2 uint16 = 12 // bzip2 24 | LZMA uint16 = 14 // LZMA 25 | ZSTD uint16 = 93 // see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT. 26 | XZ uint16 = 95 // XZ 27 | BROTLI uint16 = 121 // private 28 | ) 29 | 30 | func (b *BarrowCtx) registerCompressor(zw *zip.Writer) (uint16, error) { 31 | switch b.Compression { 32 | case "xz": 33 | zw.RegisterCompressor(XZ, func(w io.Writer) (io.WriteCloser, error) { 34 | return xz.NewWriter(w) 35 | }) 36 | case "zstd": 37 | zw.RegisterCompressor(ZSTD, func(w io.Writer) (io.WriteCloser, error) { 38 | return zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) 39 | }) 40 | case "bzip2": 41 | zw.RegisterCompressor(BZIP2, func(w io.Writer) (io.WriteCloser, error) { 42 | return bzip2.NewWriter(w, nil) 43 | }) 44 | case "deflate", "": 45 | default: 46 | return zip.Store, fmt.Errorf("unsupported zip compress method '%s'", b.Compression) 47 | } 48 | return zip.Deflate, nil 49 | } 50 | 51 | func (b *BarrowCtx) addItem2Zip(z *zip.Writer, item *FileItem, method uint16, prefix string) error { 52 | itemPath := filepath.Join(b.CWD, item.Path) 53 | si, err := os.Stat(itemPath) 54 | if err != nil { 55 | return err 56 | } 57 | var nameInArchive string 58 | switch { 59 | case len(item.Rename) != 0: 60 | nameInArchive = filepath.Join(prefix, item.Destination, item.Rename) 61 | default: 62 | nameInArchive = filepath.Join(prefix, item.Destination, filepath.Base(item.Path)) 63 | } 64 | hdr, err := zip.FileInfoHeader(si) 65 | if err != nil { 66 | return err 67 | } 68 | if len(item.Permissions) != 0 { 69 | if m, err := strconv.ParseInt(item.Permissions, 8, 64); err == nil { 70 | hdr.SetMode(fs.FileMode(m)) 71 | } 72 | } 73 | if si.IsDir() { 74 | hdr.Name = ToNixPath(nameInArchive) + "/" 75 | hdr.Method = zip.Store 76 | if _, err = z.CreateHeader(hdr); err != nil { 77 | return err 78 | } 79 | return nil 80 | } 81 | hdr.Name = ToNixPath(nameInArchive) 82 | hdr.Method = method 83 | hdr.Modified = si.ModTime() 84 | w, err := z.CreateHeader(hdr) 85 | if err != nil { 86 | return fmt.Errorf("create zip header error: %w", err) 87 | } 88 | fd, err := os.Open(itemPath) 89 | if err != nil { 90 | return err 91 | } 92 | defer fd.Close() 93 | if _, err := io.Copy(w, fd); err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | 99 | func (b *BarrowCtx) addCrate2Zip(z *zip.Writer, crate *Crate, method uint16, prefix string) error { 100 | baseName := b.binaryName(crate.Name) 101 | out := filepath.Join(b.Out, crate.Destination, baseName) 102 | si, err := os.Lstat(out) 103 | if err != nil { 104 | return err 105 | } 106 | hdr, err := zip.FileInfoHeader(si) 107 | if err != nil { 108 | return err 109 | } 110 | nameInArchive := filepath.Join(prefix, crate.Destination, baseName) 111 | hdr.Name = ToNixPath(nameInArchive) 112 | hdr.SetMode(0755) 113 | hdr.Method = method 114 | hdr.Modified = si.ModTime() 115 | w, err := z.CreateHeader(hdr) 116 | if err != nil { 117 | return fmt.Errorf("create zip header error: %w", err) 118 | } 119 | fd, err := os.Open(out) 120 | if err != nil { 121 | return err 122 | } 123 | defer fd.Close() 124 | if _, err := io.Copy(w, fd); err != nil { 125 | return err 126 | } 127 | for _, a := range crate.Alias { 128 | aliasExpend := filepath.Join(prefix, b.ExpandEnv(b.binaryName(a))) 129 | aliasPath, err := filepath.Rel(filepath.Dir(aliasExpend), filepath.Dir(nameInArchive)) 130 | if err != nil { 131 | return err 132 | } 133 | aliasLink := filepath.Join(aliasPath, filepath.Base(nameInArchive)) 134 | aliasLink = ToNixPath(aliasLink) 135 | ah := &zip.FileHeader{ 136 | Name: ToNixPath(aliasExpend), 137 | Method: zip.Store, 138 | UncompressedSize64: uint64(len(aliasLink)), 139 | Modified: si.ModTime(), 140 | } 141 | ah.SetMode(fs.ModeSymlink) 142 | aw, err := z.CreateHeader(ah) 143 | if err != nil { 144 | return err 145 | } 146 | if _, err := io.WriteString(aw, aliasLink); err != nil { 147 | return err 148 | } 149 | } 150 | return nil 151 | } 152 | 153 | func (b *BarrowCtx) zipInternal(ctx context.Context, p *Package, crates []*Crate, zipPrefix, zipPath string, h hash.Hash) error { 154 | select { 155 | case <-ctx.Done(): 156 | return ctx.Err() 157 | default: 158 | } 159 | fd, err := os.Create(zipPath) 160 | if err != nil { 161 | return err 162 | } 163 | defer fd.Close() 164 | w := io.MultiWriter(fd, h) 165 | z := zip.NewWriter(w) // TODO 166 | method, err := b.registerCompressor(z) 167 | if err != nil { 168 | return err 169 | } 170 | _ = z.SetComment(p.Summary) 171 | for _, item := range p.Include { 172 | if err := b.addItem2Zip(z, item, method, zipPrefix); err != nil { 173 | _ = z.Close() 174 | return err 175 | } 176 | } 177 | for _, crate := range crates { 178 | if err := b.addCrate2Zip(z, crate, method, zipPrefix); err != nil { 179 | _ = z.Close() 180 | return err 181 | } 182 | } 183 | return z.Close() 184 | } 185 | 186 | func (b *BarrowCtx) zip(ctx context.Context, p *Package, crates []*Crate) error { 187 | h := sha256.New() 188 | zipPrefix := fmt.Sprintf("%s-%s-%s-%s", p.Name, p.Version, b.Target, b.Arch) 189 | var zipPath string 190 | if filepath.IsAbs(b.Destination) { 191 | zipPath = filepath.Join(b.Destination, zipPrefix+".zip") 192 | } else { 193 | zipPath = filepath.Join(b.CWD, b.Destination, zipPrefix+".zip") 194 | } 195 | _ = os.MkdirAll(filepath.Dir(zipPath), 0755) 196 | if err := b.zipInternal(ctx, p, crates, zipPrefix, zipPath, h); err != nil { 197 | fmt.Fprintf(os.Stderr, "zip errpr: %d\n", err) 198 | _ = os.RemoveAll(zipPath) 199 | return err 200 | } 201 | hashPrint(h, zipPrefix+".zip") 202 | return nil 203 | } 204 | -------------------------------------------------------------------------------- /script/bootstrap.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | pwsh -NoProfile -NoLogo -ExecutionPolicy unrestricted -File "%~dp0bootstrap.ps1" %* -------------------------------------------------------------------------------- /script/bootstrap.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | Write-Host -ForegroundColor Green "bali: compiling bali ..." 4 | $SOURCE_DIR = Split-Path -Path $PSScriptRoot 5 | $BALI_SOURCE_DIR = Join-Path $SOURCE_DIR -ChildPath "cmd/bali" 6 | 7 | $BALI_EXE = Join-Path $BALI_SOURCE_DIR -ChildPath "bali" 8 | $BALI_STAGE0_EXE = Join-Path -Path $SOURCE_DIR -ChildPath "bali" 9 | if ($PSEdition -eq "Desktop" -or $IsWindows) { 10 | $BALI_EXE += ".exe" 11 | $BALI_STAGE0_EXE += ".exe" 12 | } 13 | 14 | $ps = Start-Process -FilePath "go" -WorkingDirectory $BALI_SOURCE_DIR -ArgumentList "build" -PassThru -Wait -NoNewWindow 15 | if ($ps.ExitCode -ne 0) { 16 | Exit $ps.ExitCode 17 | } 18 | 19 | Copy-Item -Force -Path $BALI_EXE -Destination $BALI_STAGE0_EXE 20 | 21 | Write-Host -ForegroundColor Green "bali: create zip package ..." 22 | 23 | $ps = Start-Process -FilePath $BALI_STAGE0_EXE -WorkingDirectory $SOURCE_DIR -ArgumentList "--target=windows --arch=amd64 --pack=zip" -PassThru -Wait -NoNewWindow 24 | if ($ps.ExitCode -ne 0) { 25 | Exit $ps.ExitCode 26 | } 27 | 28 | $ps = Start-Process -FilePath $BALI_STAGE0_EXE -WorkingDirectory $SOURCE_DIR -ArgumentList "--target=windows --arch=arm64 --pack=zip" -PassThru -Wait -NoNewWindow 29 | if ($ps.ExitCode -ne 0) { 30 | Exit $ps.ExitCode 31 | } 32 | 33 | 34 | Write-Host -ForegroundColor Green "bali: bootstrap success" 35 | -------------------------------------------------------------------------------- /script/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_FOLDER_REL=$(dirname "$0") 4 | SCRIPT_FOLDER=$( 5 | cd "${SCRIPT_FOLDER_REL}" || exit 6 | pwd 7 | ) 8 | TOPLEVEL_SOURCE_DIR=$(dirname "${SCRIPT_FOLDER}") 9 | BALI_SOURCE_DIR="${TOPLEVEL_SOURCE_DIR}/cmd/bali" 10 | 11 | if [[ "$OSTYPE" == "msys" ]]; then 12 | SUFFIX=".exe" 13 | fi 14 | 15 | echo -e "build root \x1b[32m${TOPLEVEL_SOURCE_DIR}\x1b[0m" 16 | 17 | cd "$BALI_SOURCE_DIR" || exit 1 18 | go build 19 | cp "bali${SUFFIX}" "$TOPLEVEL_SOURCE_DIR/bali.exe" 20 | 21 | cd "${TOPLEVEL_SOURCE_DIR}" || exit 1 22 | 23 | case "$OSTYPE" in 24 | solaris*) 25 | echo "solaris unsupported" 26 | ;; 27 | darwin*) 28 | echo -e "bootstarp for \x1b[32mdarwin/amd64\x1b[0m" 29 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" '--pack=tar,sh' --target=darwin --arch=amd64; then 30 | echo "bootstrap bali failed" 31 | exit 1 32 | fi 33 | echo -e "bootstarp for \x1b[32mdarwin/arm64\x1b[0m" 34 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" '--pack=tar,sh' --target=darwin --arch=arm64; then 35 | echo "bootstrap bali failed" 36 | exit 1 37 | fi 38 | ;; 39 | linux*) 40 | echo -e "bootstarp for \x1b[32mlinux/amd64\x1b[0m" 41 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" --pack='rpm,deb,tar,sh' --target=linux --arch=amd64; then 42 | echo "bootstrap bali failed" 43 | exit 1 44 | fi 45 | echo -e "bootstarp for \x1b[32mlinux/arm64\x1b[0m" 46 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" --pack='rpm,deb,tar,sh' --target=linux --arch=arm64; then 47 | echo "bootstrap bali failed" 48 | exit 1 49 | fi 50 | ;; 51 | bsd*) 52 | echo "bsd unsupported" 53 | ;; 54 | msys*) 55 | echo -e "bootstarp for \x1b[32mwindows/amd64\x1b[0m" 56 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" --pack=zip --target=windows --arch=amd64; then 57 | echo "bootstrap bali failed" 58 | exit 1 59 | fi 60 | echo -e "bootstarp for \x1b[32mwindows/arm64\x1b[0m" 61 | if ! "${TOPLEVEL_SOURCE_DIR}/bali.exe" --pack=zip --target=windows --arch=arm64; then 62 | echo "bootstrap bali failed" 63 | exit 1 64 | fi 65 | ;; 66 | esac 67 | 68 | echo -e "\\x1b[32mbali: bootstrap success\\x1b[0m" 69 | -------------------------------------------------------------------------------- /test/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Test code -------------------------------------------------------------------------------- /test/args/args.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alecthomas/kong" 9 | ) 10 | 11 | type VersionFlag bool 12 | 13 | func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil } 14 | func (v VersionFlag) IsBool() bool { return true } 15 | func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { 16 | app.Exit(0) 17 | return nil 18 | } 19 | 20 | type Globals struct { 21 | M string `name:"module" short:"M" help:"Explicitly specify a module directory" default:"." type:"path"` 22 | B string `name:"build" short:"B" help:"Explicitly specify a build directory" default:"build" type:"path"` 23 | Verbose bool `name:"verbose" short:"V" help:"Make the operation more talkative"` 24 | Version VersionFlag `name:"version" short:"v" help:"Print version information and quit"` 25 | } 26 | 27 | type CleanCommand struct { 28 | } 29 | 30 | func (c *CleanCommand) Run(g *Globals) error { 31 | 32 | return nil 33 | } 34 | 35 | type BuildCommand struct { 36 | Target string `name:"target" short:"T" help:"Target OS for which the code is compiled" default:"${target}"` // windows/darwin 37 | Arch string `name:"arch" short:"A" help:"Target architecture for which the code is compiled" default:"${arch}"` // amd64/arm64 ... 38 | } 39 | 40 | func (c *BuildCommand) Run(g *Globals) error { 41 | enc := json.NewEncoder(os.Stderr) 42 | enc.SetIndent(" ", "") 43 | enc.Encode(g) 44 | enc.Encode(c) 45 | return nil 46 | } 47 | 48 | type App struct { 49 | Globals 50 | Build BuildCommand `cmd:"build" help:"Compile the current module (default)" default:"withargs"` 51 | Clean CleanCommand `cmd:"clean" help:"Remove generated artifacts"` 52 | } 53 | 54 | func main() { 55 | app := App{} 56 | 57 | ctx := kong.Parse(&app, 58 | kong.Name("bali"), 59 | kong.Description("Bali - Minimalist Golang build and packaging tool"), 60 | kong.UsageOnError(), 61 | kong.ConfigureHelp(kong.HelpOptions{ 62 | Compact: true, 63 | }), 64 | kong.Vars{ 65 | "target": runtime.GOOS, 66 | "arch": runtime.GOARCH, 67 | }) 68 | err := ctx.Run(&app.Globals) 69 | ctx.FatalIfErrorf(err) 70 | } 71 | -------------------------------------------------------------------------------- /test/cli.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | usage() { 4 | cat </dev/null 19 | if [ $? -eq 4 ]; then 20 | ARGS=$(getopt -a -o p:hv --long prefix:,version,help -- "$@") 21 | eval set -- "${ARGS}" 22 | while :; do 23 | case $1 in 24 | -p | --prefix) 25 | install_prefix=$2 26 | install_prefix=$(fix_slashes "${install_prefix}") 27 | shift 28 | ;; 29 | -h | --help) 30 | usage 31 | ;; 32 | -v | --version) 33 | echo "1.0.0" 34 | exit 0 35 | ;; 36 | --) 37 | shift 38 | break 39 | ;; 40 | *) 41 | echo "Internal error!" 42 | exit 1 43 | ;; 44 | esac 45 | shift 46 | done 47 | else 48 | for a in "$@"; do 49 | if echo "$a" | grep "^--prefix=" >/dev/null 2>/dev/null; then 50 | install_prefix="${a/--prefix=\///}" 51 | install_prefix=$(fix_slashes "${install_prefix}") 52 | continue 53 | fi 54 | if echo "$a" | grep "^--version" >/dev/null 2>/dev/null; then 55 | echo "1.0.0" 56 | exit 0 57 | fi 58 | if echo "$a" | grep "^--prefix" >/dev/null 2>/dev/null; then 59 | echo -e "error: \x1b[31m--prefix /path/to/prefix\x1b[0m is not support, switch to: \x1b[31m--prefix=/path/to/prefix\x1b[0m" 60 | exit 1 61 | fi 62 | if echo "$a" | grep "^--help" >/dev/null 2>/dev/null; then 63 | usage 64 | fi 65 | done 66 | fi 67 | 68 | prefix=$(pwd) 69 | if [[ "x${install_prefix}" != "x" ]]; then 70 | prefix="${install_prefix}" 71 | fi 72 | package=$(basename "$0") 73 | echo -e "The ${package} will be extracted to: \\x1b[32m${prefix}\\x1b[0m" 74 | -------------------------------------------------------------------------------- /test/ico/circular-chart-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-128.png -------------------------------------------------------------------------------- /test/ico/circular-chart-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-16.png -------------------------------------------------------------------------------- /test/ico/circular-chart-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-24.png -------------------------------------------------------------------------------- /test/ico/circular-chart-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-256.png -------------------------------------------------------------------------------- /test/ico/circular-chart-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-32.png -------------------------------------------------------------------------------- /test/ico/circular-chart-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart-64.png -------------------------------------------------------------------------------- /test/ico/circular-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart.png -------------------------------------------------------------------------------- /test/ico/circular-chart.png.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balibuild/bali/dba8ad68e80dd643e7791e965beea5dd2514a07f/test/ico/circular-chart.png.ico -------------------------------------------------------------------------------- /test/ico/ico.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "image" 7 | "image/png" 8 | "os" 9 | 10 | "github.com/balibuild/bali/v3/makeico" 11 | ) 12 | 13 | // IconMaker icon maker 14 | type IconMaker struct { 15 | images []image.Image 16 | fds []*os.File 17 | } 18 | 19 | // Close close 20 | func (m *IconMaker) Close() error { 21 | var err error 22 | for _, f := range m.fds { 23 | if e := f.Close(); e != nil { 24 | err = e 25 | } 26 | } 27 | return err 28 | } 29 | 30 | // AddImage add image path 31 | func (m *IconMaker) AddImage(name string) error { 32 | fd, err := os.Open(name) 33 | if err != nil { 34 | fmt.Fprintf(os.Stderr, "unable open file: %v\n", err) 35 | return err 36 | } 37 | m.fds = append(m.fds, fd) 38 | img, err := png.Decode(fd) 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "unable decode png: %v\n", err) 41 | return err 42 | } 43 | m.images = append(m.images, img) 44 | return nil 45 | } 46 | 47 | // Make create icon 48 | func (m *IconMaker) Make() error { 49 | if len(m.fds) == 0 { 50 | return errors.New("no image add") 51 | } 52 | name := m.fds[0].Name() + ".ico" 53 | fd, err := os.Create(name) 54 | if err != nil { 55 | return err 56 | } 57 | defer fd.Close() 58 | if err := makeico.EncodePNG(fd, m.images...); err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | // .\ico.exe .\circular-chart.png .\circular-chart-256.png .\circular-chart-128.png .\circular-chart-64.png .\circular-chart-32.png .\circular-chart-24.png .\circular-chart-16.png 65 | 66 | func main() { 67 | if len(os.Args) < 2 { 68 | fmt.Fprintf(os.Stderr, "usage: %s png\n", os.Args[0]) 69 | os.Exit(1) 70 | } 71 | var m IconMaker 72 | defer m.Close() 73 | for i := 1; i < len(os.Args); i++ { 74 | m.AddImage(os.Args[i]) 75 | } 76 | if err := m.Make(); err != nil { 77 | fmt.Fprintf(os.Stderr, "build icon error: %v\n", err) 78 | os.Exit(1) 79 | } 80 | fmt.Fprintf(os.Stderr, "convert png to ico success\n") 81 | } 82 | -------------------------------------------------------------------------------- /test/rpmsimple/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // rpmsample creates an rpm file with some known files, which 16 | // can be used to test rpmpack's output against other rpm implementations. 17 | // It is also an instructive example for using rpmpack. 18 | package main 19 | 20 | import ( 21 | "flag" 22 | "log" 23 | "os" 24 | 25 | "github.com/google/rpmpack" 26 | ) 27 | 28 | func main() { 29 | 30 | sign := flag.Bool("sign", false, "sign the package with a fake sig") 31 | flag.Parse() 32 | 33 | r, err := rpmpack.NewRPM(rpmpack.RPMMetaData{ 34 | Name: "rpmsample", 35 | Version: "0.1", 36 | Release: "A", 37 | Arch: "noarch", 38 | }) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | r.AddFile( 43 | rpmpack.RPMFile{ 44 | Name: "/var/lib/rpmpack", 45 | Mode: 040755, 46 | Owner: "root", 47 | Group: "root", 48 | }) 49 | r.AddFile( 50 | rpmpack.RPMFile{ 51 | Name: "/var/lib/rpmpack/sample.txt", 52 | Body: []byte("testsample\n"), 53 | Mode: 0600, 54 | Owner: "root", 55 | Group: "root", 56 | }) 57 | r.AddFile( 58 | rpmpack.RPMFile{ 59 | Name: "/var/lib/rpmpack/sample2.txt", 60 | Body: []byte("testsample2\n"), 61 | Mode: 0644, 62 | Owner: "root", 63 | Group: "root", 64 | }) 65 | r.AddFile( 66 | rpmpack.RPMFile{ 67 | Name: "/var/lib/rpmpack/sample3_link.txt", 68 | Body: []byte("/var/lib/rpmpack/sample.txt"), 69 | Mode: 0120777, 70 | Owner: "root", 71 | Group: "root", 72 | }) 73 | r.AddFile( 74 | rpmpack.RPMFile{ 75 | Name: "/var/lib/rpmpack/sample4_ghost.txt", 76 | Mode: 0644, 77 | Owner: "root", 78 | Group: "root", 79 | Type: rpmpack.GhostFile, 80 | }) 81 | r.AddFile( 82 | rpmpack.RPMFile{ 83 | Name: "/var/lib/thisdoesnotexist/sample.txt", 84 | Mode: 0644, 85 | Body: []byte("testsample\n"), 86 | Owner: "root", 87 | Group: "root", 88 | }) 89 | if *sign { 90 | r.SetPGPSigner(func([]byte) ([]byte, error) { 91 | return []byte(`this is not a signature`), nil 92 | }) 93 | } 94 | if err := r.Write(os.Stdout); err != nil { 95 | log.Fatalf("write failed: %v", err) 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /test/xz/xz.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/ulikunitz/xz" 10 | ) 11 | 12 | func main() { 13 | const text = "The quick brown fox jumps over the lazy dog.\n" 14 | var buf bytes.Buffer 15 | // compress text 16 | w, err := xz.NewWriter(&buf) 17 | if err != nil { 18 | log.Fatalf("xz.NewWriter error %s", err) 19 | } 20 | if _, err := io.WriteString(w, text); err != nil { 21 | log.Fatalf("WriteString error %s", err) 22 | } 23 | if err := w.Close(); err != nil { 24 | log.Fatalf("w.Close error %s", err) 25 | } 26 | os.Stderr.Write(buf.Bytes()) 27 | // decompress buffer and write output to stdout 28 | r, err := xz.NewReader(&buf) 29 | if err != nil { 30 | log.Fatalf("NewReader error %s", err) 31 | } 32 | if _, err = io.Copy(os.Stdout, r); err != nil { 33 | log.Fatalf("io.Copy error %s", err) 34 | } 35 | } 36 | --------------------------------------------------------------------------------