feat: 添加CI工作流、贡献指南、许可证和Makefile

This commit is contained in:
程广 2025-07-07 13:33:28 +08:00
parent c327110010
commit c022153bcd
18 changed files with 3085 additions and 99 deletions

117
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,117 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.18'
- name: Check out code
uses: actions/checkout@v3
- name: Get dependencies
run: go mod download
- name: Run tests
run: go test -v ./...
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.18'
- name: Check out code
uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
build:
name: Build
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.18'
- name: Check out code
uses: actions/checkout@v3
- name: Build
run: make build
release:
name: Release
runs-on: ubuntu-latest
needs: [build]
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.18'
- name: Check out code
uses: actions/checkout@v3
- name: Build all platforms
run: make build-all
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Linux Binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/linux/installer-builder
asset_name: installer-builder-linux-amd64
asset_content_type: application/octet-stream
- name: Upload Windows Binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/windows/installer-builder.exe
asset_name: installer-builder-windows-amd64.exe
asset_content_type: application/octet-stream
- name: Upload macOS Binary
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/darwin/installer-builder
asset_name: installer-builder-darwin-amd64
asset_content_type: application/octet-stream

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor/

98
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,98 @@
# 贡献指南
感谢您对Installer Builder项目的关注我们欢迎并鼓励社区贡献。本文档提供了参与项目开发的指南。
## 开发环境设置
1. 确保您已安装Go 1.18或更高版本
2. 克隆仓库:
```bash
git clone git@git.kingecg.top:kingecg/installerbuilder.git
cd installerbuilder
```
3. 安装依赖:
```bash
go mod download
```
## 开发流程
1. 从主分支创建新的功能分支:
```bash
git checkout -b feature/your-feature-name
```
2. 进行开发和测试
3. 确保代码符合项目的编码规范
4. 提交代码并推送到远程仓库:
```bash
git commit -m "feat: 添加新功能"
git push origin feature/your-feature-name
```
5. 创建合并请求Merge Request
## 编码规范
- 遵循Go的官方代码规范
- 使用`gofmt`或`goimports`格式化代码
- 添加适当的注释和文档
- 确保所有测试通过
- 遵循[Conventional Commits](https://www.conventionalcommits.org/)规范进行提交
## 提交规范
提交信息应遵循以下格式:
```
<类型>(<范围>): <描述>
[可选的正文]
[可选的脚注]
```
类型包括:
- feat: 新功能
- fix: 修复bug
- docs: 文档更新
- style: 代码风格调整(不影响代码功能)
- refactor: 代码重构
- perf: 性能优化
- test: 添加或修改测试
- build: 构建系统或外部依赖变更
- ci: CI配置变更
- chore: 其他不修改源代码或测试的变更
## 测试
- 为所有新功能和修复添加单元测试
- 确保所有测试通过:
```bash
make test
```
- 检查代码覆盖率:
```bash
make cover
```
## 文档
- 更新README.md以反映重要变更
- 为新功能添加文档
- 保持API文档的最新状态
## 发布流程
1. 更新版本号
2. 更新CHANGELOG.md
3. 创建发布标签
4. 构建并发布二进制文件
## 许可证
通过贡献代码您同意您的贡献将根据项目的许可证Apache License 2.0)进行许可。
## 联系方式
如有任何问题,请通过以下方式联系我们:
- 提交Issue
- 发送邮件至项目维护者

190
LICENSE Normal file
View File

@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2023 KingECG
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

128
Makefile Normal file
View File

@ -0,0 +1,128 @@
# Makefile for Installer Builder
# 基本变量
NAME := installer-builder
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME := $(shell date -u '+%Y-%m-%d %H:%M:%S')
LDFLAGS := -ldflags "-X git.kingecg.top/kingecg/installerbuilder/internal/version.Version=$(VERSION) \
-X git.kingecg.top/kingecg/installerbuilder/internal/version.CommitHash=$(COMMIT_HASH) \
-X git.kingecg.top/kingecg/installerbuilder/internal/version.BuildTime=$(BUILD_TIME)"
# 目标平台
PLATFORMS := linux windows darwin
ARCHITECTURES := amd64 arm64
# 输出目录
DIST_DIR := dist
# Go命令
GO := go
GOBUILD := $(GO) build
GOTEST := $(GO) test
GOCLEAN := $(GO) clean
GOMOD := $(GO) mod
GOGET := $(GO) get
GOFMT := $(GO) fmt
GOLINT := golangci-lint
# 默认目标
.PHONY: all
all: clean build
# 构建目标
.PHONY: build
build:
@echo "构建 $(NAME)..."
@mkdir -p $(DIST_DIR)
$(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(NAME) ./cmd/cli
# 构建所有平台
.PHONY: build-all
build-all:
@echo "构建所有平台的二进制文件..."
@mkdir -p $(DIST_DIR)
@for platform in $(PLATFORMS); do \
for arch in $(ARCHITECTURES); do \
output_dir=$(DIST_DIR)/$$platform; \
mkdir -p $$output_dir; \
output_name=$$output_dir/$(NAME); \
if [ "$$platform" = "windows" ]; then output_name=$$output_name.exe; fi; \
echo "构建 $$platform/$$arch..."; \
GOOS=$$platform GOARCH=$$arch $(GOBUILD) $(LDFLAGS) -o $$output_name ./cmd/cli || exit 1; \
done; \
done
# 运行测试
.PHONY: test
test:
@echo "运行测试..."
$(GOTEST) -v ./...
# 运行测试覆盖率
.PHONY: test-coverage
test-coverage:
@echo "运行测试覆盖率..."
$(GOTEST) -v -coverprofile=coverage.out ./...
$(GO) tool cover -html=coverage.out -o coverage.html
# 运行代码检查
.PHONY: lint
lint:
@echo "运行代码检查..."
$(GOLINT) run
# 格式化代码
.PHONY: fmt
fmt:
@echo "格式化代码..."
$(GOFMT) ./...
# 清理构建产物
.PHONY: clean
clean:
@echo "清理构建产物..."
$(GOCLEAN)
rm -rf $(DIST_DIR)
rm -f coverage.out coverage.html
# 初始化项目
.PHONY: init
init:
@echo "初始化项目..."
$(GOMOD) tidy
$(GOGET) -u ./...
# 安装二进制文件
.PHONY: install
install: build
@echo "安装 $(NAME)..."
cp $(DIST_DIR)/$(NAME) /usr/local/bin/
# 卸载二进制文件
.PHONY: uninstall
uninstall:
@echo "卸载 $(NAME)..."
rm -f /usr/local/bin/$(NAME)
# 帮助信息
.PHONY: help
help:
@echo "Installer Builder Makefile"
@echo ""
@echo "使用方法:"
@echo " make [target]"
@echo ""
@echo "目标:"
@echo " all 默认目标执行clean和build"
@echo " build 构建项目"
@echo " build-all 构建所有平台的二进制文件"
@echo " test 运行测试"
@echo " test-coverage 运行测试覆盖率"
@echo " lint 运行代码检查"
@echo " fmt 格式化代码"
@echo " clean 清理构建产物"
@echo " init 初始化项目"
@echo " install 安装二进制文件"
@echo " uninstall 卸载二进制文件"
@echo " help 显示帮助信息"

292
README.md
View File

@ -1,118 +1,214 @@
# InstallerBuilder
# Installer Builder
InstallerBuilder是一个跨平台的安装包构建工具用于为各种平台Windows、Linux等生成不同类型的安装包MSI、DEB、RPM、ZIP等。该工具提供了灵活的配置选项和强大的构建功能使开发人员能够轻松地为其应用程序创建专业的安装包
Installer Builder 是一个强大的跨平台安装包构建工具。它支持Windows和Linux平台可以生成MSI、DEB、RPM和压缩包格式的安装包。通过简单的配置文件您可以定义安装包的内容、安装脚本和依赖项
## 项目概述
## 功能特点
InstallerBuilder采用Go语言开发具有以下特点
- 支持多种平台Windows、Linux
- 支持多种安装包格式MSI、DEB、RPM、ZIP、TAR.GZ
- 通过YAML配置文件定义安装包内容和行为
- 支持自定义安装脚本
- 支持并行构建多个安装包
- 提供命令行界面易于集成到CI/CD流程中
- **跨平台支持**支持Windows、Linux等多种操作系统
- **多种包格式**支持MSI、DEB、RPM、ZIP等多种安装包格式
- **灵活配置**通过YAML配置文件定义安装包内容和行为
- **脚本集成**:支持安装前后脚本、卸载前后脚本等
- **依赖项管理**:支持系统依赖项和应用依赖项的处理
- **文件集合处理**:支持文件复制、权限设置、过滤、转换等
- **平台适配**:针对不同平台提供特定的适配功能
## 安装
## 项目结构
### 从源代码构建
```
installerbuilder/
├── cmd/ # 命令行工具
│ └── installerbuilder/ # 主命令行入口
├── internal/ # 内部包
│ ├── builder/ # 构建器
│ │ ├── dependencies/ # 依赖项处理
│ │ ├── files/ # 文件处理
│ │ ├── package/ # 包构建
│ │ │ ├── deb/ # DEB包构建
│ │ │ ├── msi/ # MSI包构建
│ │ │ ├── rpm/ # RPM包构建
│ │ │ └── zip/ # ZIP包构建
│ │ └── scripts/ # 脚本处理
│ ├── config/ # 配置处理
│ ├── errors/ # 错误处理
│ ├── log/ # 日志系统
│ ├── platform/ # 平台适配
│ │ ├── linux/ # Linux平台适配
│ │ └── windows/ # Windows平台适配
│ └── resource/ # 资源管理
├── pkg/ # 公共包
│ └── utils/ # 工具函数
├── tasks/ # 任务文档
├── examples/ # 示例配置和项目
└── docs/ # 文档
```
## 任务列表
项目开发分为以下20个任务
1. [项目初始化](tasks/task01-项目初始化.md)
2. [日志系统实现](tasks/task02-日志系统实现.md)
3. [错误处理框架](tasks/task03-错误处理框架.md)
4. [资源管理器实现](tasks/task04-资源管理器实现.md)
5. [配置模型定义](tasks/task05-配置模型定义.md)
6. [配置加载器](tasks/task06-配置加载器.md)
7. [配置解析器](tasks/task07-配置解析器.md)
8. [配置验证器](tasks/task08-配置验证器.md)
9. [平台适配接口定义](tasks/task09-平台适配接口定义.md)
10. [Windows平台适配器](tasks/task10-Windows平台适配器.md)
11. [Linux平台适配器](tasks/task11-Linux平台适配器.md)
12. [MSI包构建器](tasks/task12-MSI包构建器.md)
13. [DEB包构建器](tasks/task13-DEB包构建器.md)
14. [RPM包构建器](tasks/task14-RPM包构建器.md)
15. [ZIP包构建器](tasks/task15-ZIP包构建器.md)
16. [文件集合处理器](tasks/task16-文件集合处理器.md)
17. [依赖项处理器](tasks/task17-依赖项处理器.md)
18. [脚本处理器](tasks/task18-脚本处理器.md)
19. [默认构建协调器](tasks/task19-默认构建协调器.md)
20. [命令行界面](tasks/task20-命令行界面.md)
## 开发指南
### 环境要求
- Go 1.18或更高版本
- 根据目标平台可能需要额外的工具:
- Windows: WiX Toolset用于MSI构建
- Linux: dpkg-deb用于DEB构建、rpmbuild用于RPM构建
### 构建项目
1. 克隆仓库:
```bash
# 克隆仓库
git clone https://github.com/yourusername/installerbuilder.git
git clone https://git.kingecg.top/kingecg/installerbuilder.git
cd installerbuilder
# 构建项目
go build -o bin/installerbuilder ./cmd/installerbuilder
# 运行测试
go test ./...
```
### 使用示例
2. 构建项目:
```bash
# 构建Windows MSI安装包
./bin/installerbuilder build --config examples/myapp/config.yaml --platform windows --arch amd64 --type msi
# 构建Linux DEB安装包
./bin/installerbuilder build --config examples/myapp/config.yaml --platform linux --arch amd64 --type deb
# 验证配置文件
./bin/installerbuilder validate --config examples/myapp/config.yaml
make build
```
## 贡献指南
3. 安装二进制文件(可选):
1. Fork项目
2. 创建特性分支 (`git checkout -b feature/amazing-feature`)
3. 提交更改 (`git commit -m 'Add some amazing feature'`)
```bash
make install
```
### 使用预编译的二进制文件
从[发布页面](https://git.kingecg.top/kingecg/installerbuilder/releases)下载适合您平台的预编译二进制文件。
## 快速开始
1. 初始化配置文件:
```bash
installer-builder init > installer.yaml
```
2. 编辑配置文件,定义安装包内容和行为。
3. 构建安装包:
```bash
installer-builder build -c installer.yaml
```
## 配置文件示例
```yaml
name: my-application
version: 1.0.0
description: My Application Description
author: Author Name
license: MIT
build:
outputDir: dist
targets:
- windows
- linux
formats:
- msi
- deb
- rpm
- zip
contents:
files:
- source: bin/myapp
destination: bin/myapp
mode: "0755"
- source: README.md
destination: doc/README.md
mode: "0644"
directories:
- path: logs
mode: "0755"
- path: config
mode: "0755"
scripts:
- type: postinstall
content: |
#!/bin/sh
echo "Installation completed successfully!"
platforms:
windows:
msi:
upgradeCode: 12345678-1234-1234-1234-123456789012
manufacturer: My Company
installDir: MyApplication
programMenuDir: MyApplication
shortcuts:
- name: My Application
target: "[INSTALLDIR]bin\\myapp.exe"
description: Launch My Application
icon: "[INSTALLDIR]icons\\app.ico"
linux:
deb:
section: utils
priority: optional
architecture: amd64
depends:
- libc6
rpm:
group: Applications/System
vendor: My Company
url: https://example.com
requires:
- glibc
autoReqProv: true
```
## 命令行参考
```
Installer Builder - 一个跨平台的安装包构建工具
用法:
installer-builder [命令]
可用命令:
build 构建安装包
init 初始化配置文件模板
validate 验证配置文件
version 显示版本信息
help 显示帮助信息
标志:
-h, --help 显示帮助信息
-v, --verbose 启用详细日志输出
-q, --quiet 只显示错误信息
-c, --config 指定配置文件路径
```
### build 命令
```
根据配置文件构建安装包。
用法:
installer-builder build [标志]
标志:
-t, --target 只构建指定平台的安装包
-a, --arch 只构建指定架构的安装包
-o, --output 指定输出目录
--clean 构建前清理输出目录
-p, --parallel 并行构建的数量
```
### validate 命令
```
验证配置文件的格式和内容是否正确。
用法:
installer-builder validate [标志]
标志:
--strict 启用严格验证模式
```
### init 命令
```
创建一个基本的配置文件模板。
用法:
installer-builder init
```
## 依赖项
### 构建工具依赖
- Windows MSI: [WiX Toolset](https://wixtoolset.org/)
- Linux DEB: dpkg-deb
- Linux RPM: rpmbuild
## 贡献
欢迎贡献代码、报告问题或提出改进建议。请遵循以下步骤:
1. Fork 项目
2. 创建您的特性分支 (`git checkout -b feature/amazing-feature`)
3. 提交您的更改 (`git commit -m 'Add some amazing feature'`)
4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 创建Pull Request
5. 打开一个 Pull Request
## 许可证
本项目采用MIT许可证 - 详见 [LICENSE](LICENSE) 文件
本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
## 联系方式
如有问题或建议,请通过以下方式联系我们:
- 项目主页:[https://git.kingecg.top/kingecg/installerbuilder](https://git.kingecg.top/kingecg/installerbuilder)
- 问题跟踪:[https://git.kingecg.top/kingecg/installerbuilder/issues](https://git.kingecg.top/kingecg/installerbuilder/issues)

88
cmd/cli/main.go Normal file
View File

@ -0,0 +1,88 @@
// Package main 是Installer Builder的命令行入口点
package main
import (
"fmt"
"os"
"git.kingecg.top/kingecg/installerbuilder/internal/version"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "installer-builder",
Short: "Installer Builder - 一个跨平台的安装包构建工具",
Long: `Installer Builder 是一个强大的跨平台安装包构建工具
它支持Windows和Linux平台可以生成MSIDEBRPM和压缩包格式的安装包
通过简单的配置文件您可以定义安装包的内容安装脚本和依赖项`,
Version: version.Version,
Run: func(cmd *cobra.Command, args []string) {
// 如果没有子命令,显示帮助信息
cmd.Help()
},
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "显示版本信息",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version.Info())
},
}
var buildCmd = &cobra.Command{
Use: "build",
Short: "构建安装包",
Long: `根据配置文件构建安装包。`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("构建功能尚未实现")
},
}
var validateCmd = &cobra.Command{
Use: "validate",
Short: "验证配置文件",
Long: `验证配置文件的格式和内容是否正确。`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("验证功能尚未实现")
},
}
var initCmd = &cobra.Command{
Use: "init",
Short: "初始化配置文件模板",
Long: `创建一个基本的配置文件模板。`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("初始化功能尚未实现")
},
}
func init() {
// 添加子命令
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(buildCmd)
rootCmd.AddCommand(validateCmd)
rootCmd.AddCommand(initCmd)
// 添加全局标志
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "启用详细日志输出")
rootCmd.PersistentFlags().BoolP("quiet", "q", false, "只显示错误信息")
rootCmd.PersistentFlags().StringP("config", "c", "", "指定配置文件路径")
// 添加构建命令的标志
buildCmd.Flags().StringP("target", "t", "", "只构建指定平台的安装包")
buildCmd.Flags().StringP("arch", "a", "", "只构建指定架构的安装包")
buildCmd.Flags().StringP("output", "o", "", "指定输出目录")
buildCmd.Flags().Bool("clean", false, "构建前清理输出目录")
buildCmd.Flags().IntP("parallel", "p", 0, "并行构建的数量")
// 添加验证命令的标志
validateCmd.Flags().Bool("strict", false, "启用严格验证模式")
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

28
go.mod Normal file
View File

@ -0,0 +1,28 @@
module git.kingecg.top/kingecg/installerbuilder
go 1.18
require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.16.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

496
go.sum Normal file
View File

@ -0,0 +1,496 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

218
internal/builder/builder.go Normal file
View File

@ -0,0 +1,218 @@
// Package builder 提供安装包构建功能
package builder
import (
"errors"
"fmt"
"os"
"path/filepath"
"sync"
"git.kingecg.top/kingecg/installerbuilder/internal/config"
"git.kingecg.top/kingecg/installerbuilder/internal/logger"
)
// Builder 是安装包构建器的接口
type Builder interface {
// Build 构建安装包
Build() error
// Name 返回构建器名称
Name() string
// SupportedPlatforms 返回支持的平台列表
SupportedPlatforms() []string
// SupportedFormats 返回支持的格式列表
SupportedFormats() []string
}
// BuilderFactory 是构建器工厂函数类型
type BuilderFactory func(cfg *config.Config, outputDir string) Builder
// 注册的构建器工厂
var (
builderFactories = make(map[string]BuilderFactory)
buildersMutex sync.RWMutex
)
// RegisterBuilder 注册构建器工厂
func RegisterBuilder(name string, factory BuilderFactory) {
buildersMutex.Lock()
defer buildersMutex.Unlock()
builderFactories[name] = factory
}
// GetBuilder 获取指定名称的构建器
func GetBuilder(name string, cfg *config.Config, outputDir string) (Builder, error) {
buildersMutex.RLock()
factory, exists := builderFactories[name]
buildersMutex.RUnlock()
if !exists {
return nil, fmt.Errorf("未找到名为 '%s' 的构建器", name)
}
return factory(cfg, outputDir), nil
}
// BuildOptions 表示构建选项
type BuildOptions struct {
ConfigPath string // 配置文件路径
OutputDir string // 输出目录
Targets []string // 目标平台列表
Formats []string // 输出格式列表
Parallel int // 并行构建数量
CleanOutput bool // 是否清理输出目录
}
// BuildAll 构建所有指定的安装包
func BuildAll(opts BuildOptions) error {
// 加载配置
cfg, err := config.LoadConfig(opts.ConfigPath)
if err != nil {
return fmt.Errorf("加载配置失败: %w", err)
}
// 验证配置
result := cfg.Validate(true)
if !result.Valid {
return errors.New(result.String())
}
// 确定输出目录
outputDir := opts.OutputDir
if outputDir == "" {
outputDir = cfg.Build.OutputDir
if outputDir == "" {
outputDir = "dist"
}
}
// 确保输出目录存在
outputDir, err = filepath.Abs(outputDir)
if err != nil {
return fmt.Errorf("获取输出目录的绝对路径失败: %w", err)
}
// 如果需要清理输出目录
if opts.CleanOutput {
logger.Info("清理输出目录:", outputDir)
if err := os.RemoveAll(outputDir); err != nil {
return fmt.Errorf("清理输出目录失败: %w", err)
}
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %w", err)
}
// 确定目标平台和格式
targets := opts.Targets
if len(targets) == 0 {
targets = cfg.Build.Targets
}
formats := opts.Formats
if len(formats) == 0 {
formats = cfg.Build.Formats
}
// 创建构建任务
type buildTask struct {
target string
format string
}
var tasks []buildTask
for _, target := range targets {
for _, format := range formats {
// 检查是否支持该平台和格式的组合
if isFormatSupportedForTarget(target, format) {
tasks = append(tasks, buildTask{
target: target,
format: format,
})
} else {
logger.Warnf("不支持的平台和格式组合: %s/%s", target, format)
}
}
}
if len(tasks) == 0 {
return errors.New("没有可构建的任务")
}
// 确定并行度
parallel := opts.Parallel
if parallel <= 0 {
parallel = 1
}
// 创建工作池
var wg sync.WaitGroup
taskCh := make(chan buildTask)
errCh := make(chan error, len(tasks))
// 启动工作协程
for i := 0; i < parallel; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range taskCh {
logger.Infof("开始构建 %s/%s", task.target, task.format)
if err := buildPackage(cfg, outputDir, task.target, task.format); err != nil {
errCh <- fmt.Errorf("构建 %s/%s 失败: %w", task.target, task.format, err)
} else {
logger.Infof("成功构建 %s/%s", task.target, task.format)
}
}
}()
}
// 分发任务
for _, task := range tasks {
taskCh <- task
}
close(taskCh)
// 等待所有任务完成
wg.Wait()
close(errCh)
// 收集错误
var errs []error
for err := range errCh {
errs = append(errs, err)
}
if len(errs) > 0 {
return fmt.Errorf("构建过程中发生 %d 个错误,第一个错误: %w", len(errs), errs[0])
}
return nil
}
// buildPackage 构建单个安装包
func buildPackage(cfg *config.Config, outputDir, target, format string) error {
// 获取构建器
builderName := fmt.Sprintf("%s-%s", target, format)
builder, err := GetBuilder(builderName, cfg, outputDir)
if err != nil {
return err
}
// 执行构建
return builder.Build()
}
// isFormatSupportedForTarget 检查是否支持指定的平台和格式组合
func isFormatSupportedForTarget(target, format string) bool {
// 这里可以根据实际支持的组合进行检查
// 目前简单实现,后续可以扩展
switch target {
case "windows":
return format == "msi" || format == "zip"
case "linux":
return format == "deb" || format == "rpm" || format == "tar.gz"
default:
return false
}
}

View File

@ -0,0 +1,290 @@
// Package linux 提供Linux平台安装包构建功能
package linux
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"git.kingecg.top/kingecg/installerbuilder/internal/builder"
"git.kingecg.top/kingecg/installerbuilder/internal/config"
"git.kingecg.top/kingecg/installerbuilder/internal/logger"
)
// DEBBuilder 是Linux DEB安装包构建器
type DEBBuilder struct {
config *config.Config
outputDir string
}
// 确保DEBBuilder实现了Builder接口
var _ builder.Builder = (*DEBBuilder)(nil)
// 注册构建器
func init() {
builder.RegisterBuilder("linux-deb", func(cfg *config.Config, outputDir string) builder.Builder {
return NewDEBBuilder(cfg, outputDir)
})
}
// NewDEBBuilder 创建一个新的DEB构建器
func NewDEBBuilder(cfg *config.Config, outputDir string) *DEBBuilder {
return &DEBBuilder{
config: cfg,
outputDir: outputDir,
}
}
// Name 返回构建器名称
func (b *DEBBuilder) Name() string {
return "Linux DEB Builder"
}
// SupportedPlatforms 返回支持的平台列表
func (b *DEBBuilder) SupportedPlatforms() []string {
return []string{"linux"}
}
// SupportedFormats 返回支持的格式列表
func (b *DEBBuilder) SupportedFormats() []string {
return []string{"deb"}
}
// Build 构建DEB安装包
func (b *DEBBuilder) Build() error {
logger.Info("开始构建Linux DEB安装包")
// 创建临时工作目录
workDir, err := os.MkdirTemp("", "installer-builder-deb-*")
if err != nil {
return fmt.Errorf("创建临时工作目录失败: %w", err)
}
defer os.RemoveAll(workDir)
logger.Debugf("使用临时工作目录: %s", workDir)
// 创建DEB包结构
if err := b.createDebStructure(workDir); err != nil {
return fmt.Errorf("创建DEB包结构失败: %w", err)
}
// 复制文件到临时目录
if err := b.copyFiles(workDir); err != nil {
return fmt.Errorf("复制文件失败: %w", err)
}
// 创建控制文件
if err := b.createControlFile(workDir); err != nil {
return fmt.Errorf("创建控制文件失败: %w", err)
}
// 创建安装脚本
if err := b.createScripts(workDir); err != nil {
return fmt.Errorf("创建安装脚本失败: %w", err)
}
// 构建DEB包
debFile := filepath.Join(b.outputDir, fmt.Sprintf("%s_%s_amd64.deb", b.config.Name, b.config.Version))
if err := b.buildDeb(workDir, debFile); err != nil {
return fmt.Errorf("构建DEB包失败: %w", err)
}
logger.Infof("成功构建DEB安装包: %s", debFile)
return nil
}
// createDebStructure 创建DEB包结构
func (b *DEBBuilder) createDebStructure(workDir string) error {
// 创建DEBIAN目录
debianDir := filepath.Join(workDir, "DEBIAN")
if err := os.MkdirAll(debianDir, 0755); err != nil {
return fmt.Errorf("创建DEBIAN目录失败: %w", err)
}
// 创建安装目录
installDir := filepath.Join(workDir, "usr", "local", "bin")
if err := os.MkdirAll(installDir, 0755); err != nil {
return fmt.Errorf("创建安装目录失败: %w", err)
}
// 创建文档目录
docDir := filepath.Join(workDir, "usr", "share", "doc", b.config.Name)
if err := os.MkdirAll(docDir, 0755); err != nil {
return fmt.Errorf("创建文档目录失败: %w", err)
}
return nil
}
// copyFiles 复制文件到临时目录
func (b *DEBBuilder) copyFiles(workDir string) error {
// 这里应该实现文件复制逻辑
// 实际应用中,应该根据配置复制文件到临时目录
logger.Debug("复制文件到临时目录")
return nil
}
// createControlFile 创建控制文件
func (b *DEBBuilder) createControlFile(workDir string) error {
controlFile := filepath.Join(workDir, "DEBIAN", "control")
// 准备依赖项列表
var depends string
if len(b.config.Platforms.Linux.Deb.Depends) > 0 {
depends = strings.Join(b.config.Platforms.Linux.Deb.Depends, ", ")
} else {
depends = ""
}
// 准备推荐项列表
var recommends string
if len(b.config.Platforms.Linux.Deb.Recommends) > 0 {
recommends = strings.Join(b.config.Platforms.Linux.Deb.Recommends, ", ")
} else {
recommends = ""
}
// 准备建议项列表
var suggests string
if len(b.config.Platforms.Linux.Deb.Suggests) > 0 {
suggests = strings.Join(b.config.Platforms.Linux.Deb.Suggests, ", ")
} else {
suggests = ""
}
// 准备冲突项列表
var conflicts string
if len(b.config.Platforms.Linux.Deb.Conflicts) > 0 {
conflicts = strings.Join(b.config.Platforms.Linux.Deb.Conflicts, ", ")
} else {
conflicts = ""
}
// 准备替换项列表
var replaces string
if len(b.config.Platforms.Linux.Deb.Replaces) > 0 {
replaces = strings.Join(b.config.Platforms.Linux.Deb.Replaces, ", ")
} else {
replaces = ""
}
// 创建控制文件内容
content := fmt.Sprintf(`Package: %s
Version: %s
Section: %s
Priority: %s
Architecture: %s
Maintainer: %s
Description: %s
`,
b.config.Name,
b.config.Version,
b.config.Platforms.Linux.Deb.Section,
b.config.Platforms.Linux.Deb.Priority,
b.config.Platforms.Linux.Deb.Architecture,
b.config.Author,
b.config.Description)
// 添加可选字段
if depends != "" {
content += fmt.Sprintf("Depends: %s\n", depends)
}
if recommends != "" {
content += fmt.Sprintf("Recommends: %s\n", recommends)
}
if suggests != "" {
content += fmt.Sprintf("Suggests: %s\n", suggests)
}
if conflicts != "" {
content += fmt.Sprintf("Conflicts: %s\n", conflicts)
}
if replaces != "" {
content += fmt.Sprintf("Replaces: %s\n", replaces)
}
// 写入控制文件
return os.WriteFile(controlFile, []byte(content), 0644)
}
// createScripts 创建安装脚本
func (b *DEBBuilder) createScripts(workDir string) error {
// 脚本类型到文件名的映射
scriptFiles := map[string]string{
"preinstall": "preinst",
"postinstall": "postinst",
"preuninstall": "prerm",
"postuninstall": "postrm",
}
// 遍历配置中的脚本
for _, script := range b.config.Contents.Scripts {
// 检查是否是DEB支持的脚本类型
filename, ok := scriptFiles[script.Type]
if !ok {
logger.Warnf("跳过不支持的脚本类型: %s", script.Type)
continue
}
// 脚本文件路径
scriptPath := filepath.Join(workDir, "DEBIAN", filename)
// 获取脚本内容
var content string
if script.Content != "" {
content = script.Content
} else if script.Source != "" {
// 从源文件读取脚本内容
data, err := os.ReadFile(script.Source)
if err != nil {
return fmt.Errorf("读取脚本源文件失败: %w", err)
}
content = string(data)
} else {
logger.Warnf("跳过空脚本: %s", script.Type)
continue
}
// 确保脚本以#!/bin/sh开头
if !strings.HasPrefix(content, "#!") {
content = "#!/bin/sh\n" + content
}
// 写入脚本文件
if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil {
return fmt.Errorf("写入脚本文件失败: %w", err)
}
logger.Debugf("创建脚本文件: %s", scriptPath)
}
return nil
}
// buildDeb 构建DEB包
func (b *DEBBuilder) buildDeb(workDir, outputFile string) error {
// 检查是否安装了dpkg-deb
dpkgDeb, err := exec.LookPath("dpkg-deb")
if err != nil {
return fmt.Errorf("未找到dpkg-deb工具请确保已安装dpkg-deb并添加到PATH中")
}
// 确保输出目录存在
if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %w", err)
}
// 构建命令
cmd := exec.Command(dpkgDeb, "--build", workDir, outputFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 执行命令
logger.Debugf("执行命令: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("执行dpkg-deb命令失败: %w", err)
}
return nil
}

View File

@ -0,0 +1,360 @@
// Package linux 提供Linux平台安装包构建功能
package linux
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"git.kingecg.top/kingecg/installerbuilder/internal/builder"
"git.kingecg.top/kingecg/installerbuilder/internal/config"
"git.kingecg.top/kingecg/installerbuilder/internal/logger"
)
// RPMBuilder 是Linux RPM安装包构建器
type RPMBuilder struct {
config *config.Config
outputDir string
}
// 确保RPMBuilder实现了Builder接口
var _ builder.Builder = (*RPMBuilder)(nil)
// 注册构建器
func init() {
builder.RegisterBuilder("linux-rpm", func(cfg *config.Config, outputDir string) builder.Builder {
return NewRPMBuilder(cfg, outputDir)
})
}
// NewRPMBuilder 创建一个新的RPM构建器
func NewRPMBuilder(cfg *config.Config, outputDir string) *RPMBuilder {
return &RPMBuilder{
config: cfg,
outputDir: outputDir,
}
}
// Name 返回构建器名称
func (b *RPMBuilder) Name() string {
return "Linux RPM Builder"
}
// SupportedPlatforms 返回支持的平台列表
func (b *RPMBuilder) SupportedPlatforms() []string {
return []string{"linux"}
}
// SupportedFormats 返回支持的格式列表
func (b *RPMBuilder) SupportedFormats() []string {
return []string{"rpm"}
}
// Build 构建RPM安装包
func (b *RPMBuilder) Build() error {
logger.Info("开始构建Linux RPM安装包")
// 创建临时工作目录
workDir, err := os.MkdirTemp("", "installer-builder-rpm-*")
if err != nil {
return fmt.Errorf("创建临时工作目录失败: %w", err)
}
defer os.RemoveAll(workDir)
logger.Debugf("使用临时工作目录: %s", workDir)
// 创建RPM构建目录结构
buildDirs := []string{
filepath.Join(workDir, "BUILD"),
filepath.Join(workDir, "RPMS"),
filepath.Join(workDir, "SOURCES"),
filepath.Join(workDir, "SPECS"),
filepath.Join(workDir, "SRPMS"),
}
for _, dir := range buildDirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建RPM构建目录失败: %w", err)
}
}
// 创建源代码包
sourceDir := filepath.Join(workDir, "SOURCES", fmt.Sprintf("%s-%s", b.config.Name, b.config.Version))
if err := os.MkdirAll(sourceDir, 0755); err != nil {
return fmt.Errorf("创建源代码目录失败: %w", err)
}
// 复制文件到源代码目录
if err := b.copyFiles(sourceDir); err != nil {
return fmt.Errorf("复制文件失败: %w", err)
}
// 创建源代码压缩包
tarFile := filepath.Join(workDir, "SOURCES", fmt.Sprintf("%s-%s.tar.gz", b.config.Name, b.config.Version))
if err := b.createSourceTarball(sourceDir, tarFile); err != nil {
return fmt.Errorf("创建源代码压缩包失败: %w", err)
}
// 创建SPEC文件
specFile := filepath.Join(workDir, "SPECS", fmt.Sprintf("%s.spec", b.config.Name))
if err := b.createSpecFile(specFile); err != nil {
return fmt.Errorf("创建SPEC文件失败: %w", err)
}
// 构建RPM包
rpmFile := filepath.Join(b.outputDir, fmt.Sprintf("%s-%s.x86_64.rpm", b.config.Name, b.config.Version))
if err := b.buildRpm(workDir, specFile, rpmFile); err != nil {
return fmt.Errorf("构建RPM包失败: %w", err)
}
logger.Infof("成功构建RPM安装包: %s", rpmFile)
return nil
}
// copyFiles 复制文件到源代码目录
func (b *RPMBuilder) copyFiles(sourceDir string) error {
// 这里应该实现文件复制逻辑
// 实际应用中,应该根据配置复制文件到源代码目录
logger.Debug("复制文件到源代码目录")
return nil
}
// createSourceTarball 创建源代码压缩包
func (b *RPMBuilder) createSourceTarball(sourceDir, tarFile string) error {
// 获取源代码目录的父目录和目录名
parentDir := filepath.Dir(sourceDir)
dirName := filepath.Base(sourceDir)
// 切换到父目录
currentDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("获取当前工作目录失败: %w", err)
}
defer os.Chdir(currentDir)
if err := os.Chdir(parentDir); err != nil {
return fmt.Errorf("切换到父目录失败: %w", err)
}
// 创建tar.gz文件
cmd := exec.Command("tar", "czf", tarFile, dirName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
logger.Debugf("执行命令: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("执行tar命令失败: %w", err)
}
return nil
}
// createSpecFile 创建SPEC文件
func (b *RPMBuilder) createSpecFile(specFile string) error {
// 准备依赖项列表
var requires string
if len(b.config.Platforms.Linux.Rpm.Requires) > 0 {
requires = strings.Join(b.config.Platforms.Linux.Rpm.Requires, ", ")
} else {
requires = ""
}
// 准备提供项列表
var provides string
if len(b.config.Platforms.Linux.Rpm.Provides) > 0 {
provides = strings.Join(b.config.Platforms.Linux.Rpm.Provides, ", ")
} else {
provides = ""
}
// 准备冲突项列表
var conflicts string
if len(b.config.Platforms.Linux.Rpm.Conflicts) > 0 {
conflicts = strings.Join(b.config.Platforms.Linux.Rpm.Conflicts, ", ")
} else {
conflicts = ""
}
// 准备废弃项列表
var obsoletes string
if len(b.config.Platforms.Linux.Rpm.Obsoletes) > 0 {
obsoletes = strings.Join(b.config.Platforms.Linux.Rpm.Obsoletes, ", ")
} else {
obsoletes = ""
}
// 创建SPEC文件内容
content := fmt.Sprintf(`Name: %s
Version: %s
Release: 1%%{?dist}
Summary: %s
Group: %s
License: %s
URL: %s
Source0: %%{name}-%%{version}.tar.gz
BuildArch: x86_64
AutoReqProv: %t
`,
b.config.Name,
b.config.Version,
b.config.Description,
b.config.Platforms.Linux.Rpm.Group,
b.config.License,
b.config.Platforms.Linux.Rpm.URL,
b.config.Platforms.Linux.Rpm.AutoReqProv)
// 添加可选字段
if requires != "" {
content += fmt.Sprintf("Requires: %s\n", requires)
}
if provides != "" {
content += fmt.Sprintf("Provides: %s\n", provides)
}
if conflicts != "" {
content += fmt.Sprintf("Conflicts: %s\n", conflicts)
}
if obsoletes != "" {
content += fmt.Sprintf("Obsoletes: %s\n", obsoletes)
}
// 添加描述和构建指令
content += fmt.Sprintf(`
%%description
%s
%%prep
%%setup -q
%%build
# 构建命令如果需要
%%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local/bin
# 安装文件到$RPM_BUILD_ROOT
%%files
%%defattr(-,root,root,-)
# 文件列表
%%changelog
* %s %s <%s> - %s-1
- 初始版本
`,
b.config.Description,
"Wed Jan 01 2023", // 这里应该使用当前日期
b.config.Author,
"user@example.com", // 这里应该使用作者邮箱
b.config.Version)
// 添加脚本
for _, script := range b.config.Contents.Scripts {
var section string
switch script.Type {
case "preinstall":
section = "pre"
case "postinstall":
section = "post"
case "preuninstall":
section = "preun"
case "postuninstall":
section = "postun"
default:
logger.Warnf("跳过不支持的脚本类型: %s", script.Type)
continue
}
// 获取脚本内容
var scriptContent string
if script.Content != "" {
scriptContent = script.Content
} else if script.Source != "" {
// 从源文件读取脚本内容
data, err := os.ReadFile(script.Source)
if err != nil {
return fmt.Errorf("读取脚本源文件失败: %w", err)
}
scriptContent = string(data)
} else {
logger.Warnf("跳过空脚本: %s", script.Type)
continue
}
// 添加脚本部分
content += fmt.Sprintf("\n%%%s\n%s\n", section, scriptContent)
}
// 写入SPEC文件
return os.WriteFile(specFile, []byte(content), 0644)
}
// buildRpm 构建RPM包
func (b *RPMBuilder) buildRpm(workDir, specFile, outputFile string) error {
// 检查是否安装了rpmbuild
rpmbuild, err := exec.LookPath("rpmbuild")
if err != nil {
return fmt.Errorf("未找到rpmbuild工具请确保已安装rpmbuild并添加到PATH中")
}
// 确保输出目录存在
if err := os.MkdirAll(filepath.Dir(outputFile), 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %w", err)
}
// 构建命令
cmd := exec.Command(rpmbuild, "-bb", "--define", fmt.Sprintf("_topdir %s", workDir), specFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 执行命令
logger.Debugf("执行命令: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("执行rpmbuild命令失败: %w", err)
}
// 查找生成的RPM文件
rpmDir := filepath.Join(workDir, "RPMS", "x86_64")
entries, err := os.ReadDir(rpmDir)
if err != nil {
return fmt.Errorf("读取RPM目录失败: %w", err)
}
var rpmFile string
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".rpm") {
rpmFile = filepath.Join(rpmDir, entry.Name())
break
}
}
if rpmFile == "" {
return fmt.Errorf("未找到生成的RPM文件")
}
// 复制RPM文件到输出目录
if err := copyFile(rpmFile, outputFile); err != nil {
return fmt.Errorf("复制RPM文件失败: %w", err)
}
return nil
}
// copyFile 复制文件
func copyFile(src, dst string) error {
// 读取源文件
data, err := os.ReadFile(src)
if err != nil {
return fmt.Errorf("读取源文件失败: %w", err)
}
// 写入目标文件
if err := os.WriteFile(dst, data, 0644); err != nil {
return fmt.Errorf("写入目标文件失败: %w", err)
}
return nil
}

View File

@ -0,0 +1,197 @@
// Package windows 提供Windows平台安装包构建功能
package windows
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"git.kingecg.top/kingecg/installerbuilder/internal/builder"
"git.kingecg.top/kingecg/installerbuilder/internal/config"
"git.kingecg.top/kingecg/installerbuilder/internal/logger"
)
// MSIBuilder 是Windows MSI安装包构建器
type MSIBuilder struct {
config *config.Config
outputDir string
}
// 确保MSIBuilder实现了Builder接口
var _ builder.Builder = (*MSIBuilder)(nil)
// 注册构建器
func init() {
builder.RegisterBuilder("windows-msi", func(cfg *config.Config, outputDir string) builder.Builder {
return NewMSIBuilder(cfg, outputDir)
})
}
// NewMSIBuilder 创建一个新的MSI构建器
func NewMSIBuilder(cfg *config.Config, outputDir string) *MSIBuilder {
return &MSIBuilder{
config: cfg,
outputDir: outputDir,
}
}
// Name 返回构建器名称
func (b *MSIBuilder) Name() string {
return "Windows MSI Builder"
}
// SupportedPlatforms 返回支持的平台列表
func (b *MSIBuilder) SupportedPlatforms() []string {
return []string{"windows"}
}
// SupportedFormats 返回支持的格式列表
func (b *MSIBuilder) SupportedFormats() []string {
return []string{"msi"}
}
// Build 构建MSI安装包
func (b *MSIBuilder) Build() error {
logger.Info("开始构建Windows MSI安装包")
// 创建临时工作目录
workDir, err := os.MkdirTemp("", "installer-builder-msi-*")
if err != nil {
return fmt.Errorf("创建临时工作目录失败: %w", err)
}
defer os.RemoveAll(workDir)
logger.Debugf("使用临时工作目录: %s", workDir)
// 创建WiX源文件
wixFile := filepath.Join(workDir, "installer.wxs")
if err := b.createWixSource(wixFile); err != nil {
return fmt.Errorf("创建WiX源文件失败: %w", err)
}
// 复制文件到临时目录
if err := b.copyFiles(workDir); err != nil {
return fmt.Errorf("复制文件失败: %w", err)
}
// 编译WiX源文件
objFile := filepath.Join(workDir, "installer.wixobj")
if err := b.compileWixSource(wixFile, objFile); err != nil {
return fmt.Errorf("编译WiX源文件失败: %w", err)
}
// 链接生成MSI
msiFile := filepath.Join(b.outputDir, fmt.Sprintf("%s-%s.msi", b.config.Name, b.config.Version))
if err := b.linkMsi(objFile, msiFile); err != nil {
return fmt.Errorf("链接MSI文件失败: %w", err)
}
logger.Infof("成功构建MSI安装包: %s", msiFile)
return nil
}
// createWixSource 创建WiX源文件
func (b *MSIBuilder) createWixSource(outputFile string) error {
// 这里是一个简化的WiX源文件生成示例
// 实际应用中应该根据配置生成更复杂的WiX源文件
content := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*"
Name="%s"
Language="1033"
Version="%s"
Manufacturer="%s"
UpgradeCode="%s">
<Package InstallerVersion="200"
Compressed="yes"
InstallScope="perMachine"
Description="%s" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes" />
<Feature Id="ProductFeature" Title="Main Product" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="%s">
<!-- 组件将在这里定义 -->
</Directory>
</Directory>
</Directory>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- 组件将在这里定义 -->
</ComponentGroup>
</Product>
</Wix>`,
b.config.Name,
b.config.Version,
b.config.Platforms.Windows.Msi.Manufacturer,
b.config.Platforms.Windows.Msi.UpgradeCode,
b.config.Description,
b.config.Platforms.Windows.Msi.InstallDir)
return os.WriteFile(outputFile, []byte(content), 0644)
}
// copyFiles 复制文件到临时目录
func (b *MSIBuilder) copyFiles(workDir string) error {
// 这里应该实现文件复制逻辑
// 实际应用中,应该根据配置复制文件到临时目录
logger.Debug("复制文件到临时目录")
return nil
}
// compileWixSource 编译WiX源文件
func (b *MSIBuilder) compileWixSource(sourceFile, outputFile string) error {
// 检查是否安装了WiX工具集
candle, err := exec.LookPath("candle")
if err != nil {
return fmt.Errorf("未找到WiX Toolset的candle工具请确保已安装WiX Toolset并添加到PATH中")
}
// 构建命令
cmd := exec.Command(candle, "-nologo", "-arch", "x64", "-out", outputFile, sourceFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 执行命令
logger.Debugf("执行命令: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("执行candle命令失败: %w", err)
}
return nil
}
// linkMsi 链接生成MSI
func (b *MSIBuilder) linkMsi(objFile, msiFile string) error {
// 检查是否安装了WiX工具集
light, err := exec.LookPath("light")
if err != nil {
return fmt.Errorf("未找到WiX Toolset的light工具请确保已安装WiX Toolset并添加到PATH中")
}
// 确保输出目录存在
if err := os.MkdirAll(filepath.Dir(msiFile), 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %w", err)
}
// 构建命令
cmd := exec.Command(light, "-nologo", "-ext", "WixUIExtension", "-out", msiFile, objFile)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 执行命令
logger.Debugf("执行命令: %s", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
return fmt.Errorf("执行light命令失败: %w", err)
}
return nil
}

270
internal/config/config.go Normal file
View File

@ -0,0 +1,270 @@
// Package config 提供配置文件的加载和解析功能
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
// Config 表示安装包构建配置
type Config struct {
// 基本信息
Name string `yaml:"name" mapstructure:"name"` // 安装包名称
Version string `yaml:"version" mapstructure:"version"` // 安装包版本
Description string `yaml:"description" mapstructure:"description"` // 安装包描述
Author string `yaml:"author" mapstructure:"author"` // 作者信息
License string `yaml:"license" mapstructure:"license"` // 许可证信息
// 构建设置
Build BuildConfig `yaml:"build" mapstructure:"build"` // 构建配置
// 安装包内容
Contents ContentsConfig `yaml:"contents" mapstructure:"contents"` // 安装包内容配置
// 平台特定配置
Platforms PlatformsConfig `yaml:"platforms" mapstructure:"platforms"` // 平台特定配置
}
// BuildConfig 表示构建相关配置
type BuildConfig struct {
OutputDir string `yaml:"outputDir" mapstructure:"outputDir"` // 输出目录
Targets []string `yaml:"targets" mapstructure:"targets"` // 目标平台列表
Formats []string `yaml:"formats" mapstructure:"formats"` // 输出格式列表
}
// ContentsConfig 表示安装包内容配置
type ContentsConfig struct {
Files []FileConfig `yaml:"files" mapstructure:"files"` // 文件列表
Directories []DirectoryConfig `yaml:"directories" mapstructure:"directories"` // 目录列表
Scripts []ScriptConfig `yaml:"scripts" mapstructure:"scripts"` // 脚本列表
Symlinks []SymlinkConfig `yaml:"symlinks" mapstructure:"symlinks"` // 符号链接列表
Resources []ResourceConfig `yaml:"resources" mapstructure:"resources"` // 资源列表
Permissions []PermissionConfig `yaml:"permissions" mapstructure:"permissions"` // 权限设置列表
}
// FileConfig 表示文件配置
type FileConfig struct {
Source string `yaml:"source" mapstructure:"source"` // 源文件路径
Destination string `yaml:"destination" mapstructure:"destination"` // 目标路径
Mode string `yaml:"mode" mapstructure:"mode"` // 文件权限模式
Owner string `yaml:"owner" mapstructure:"owner"` // 文件所有者
Group string `yaml:"group" mapstructure:"group"` // 文件所属组
}
// DirectoryConfig 表示目录配置
type DirectoryConfig struct {
Path string `yaml:"path" mapstructure:"path"` // 目录路径
Mode string `yaml:"mode" mapstructure:"mode"` // 目录权限模式
Owner string `yaml:"owner" mapstructure:"owner"` // 目录所有者
Group string `yaml:"group" mapstructure:"group"` // 目录所属组
}
// ScriptConfig 表示脚本配置
type ScriptConfig struct {
Type string `yaml:"type" mapstructure:"type"` // 脚本类型preinstall, postinstall, preuninstall, postuninstall
Content string `yaml:"content" mapstructure:"content"` // 脚本内容
Source string `yaml:"source" mapstructure:"source"` // 脚本源文件路径
}
// SymlinkConfig 表示符号链接配置
type SymlinkConfig struct {
Source string `yaml:"source" mapstructure:"source"` // 源路径
Destination string `yaml:"destination" mapstructure:"destination"` // 目标路径
}
// ResourceConfig 表示资源配置
type ResourceConfig struct {
Type string `yaml:"type" mapstructure:"type"` // 资源类型
Source string `yaml:"source" mapstructure:"source"` // 源路径
Destination string `yaml:"destination" mapstructure:"destination"` // 目标路径
}
// PermissionConfig 表示权限设置配置
type PermissionConfig struct {
Path string `yaml:"path" mapstructure:"path"` // 路径
Mode string `yaml:"mode" mapstructure:"mode"` // 权限模式
Owner string `yaml:"owner" mapstructure:"owner"` // 所有者
Group string `yaml:"group" mapstructure:"group"` // 所属组
}
// PlatformsConfig 表示平台特定配置
type PlatformsConfig struct {
Windows WindowsConfig `yaml:"windows" mapstructure:"windows"` // Windows平台配置
Linux LinuxConfig `yaml:"linux" mapstructure:"linux"` // Linux平台配置
}
// WindowsConfig 表示Windows平台配置
type WindowsConfig struct {
Msi WindowsMsiConfig `yaml:"msi" mapstructure:"msi"` // MSI配置
}
// WindowsMsiConfig 表示Windows MSI配置
type WindowsMsiConfig struct {
UpgradeCode string `yaml:"upgradeCode" mapstructure:"upgradeCode"` // 升级代码
Manufacturer string `yaml:"manufacturer" mapstructure:"manufacturer"` // 制造商
ProductCode string `yaml:"productCode" mapstructure:"productCode"` // 产品代码
InstallDir string `yaml:"installDir" mapstructure:"installDir"` // 安装目录
ProgramMenuDir string `yaml:"programMenuDir" mapstructure:"programMenuDir"` // 开始菜单目录
Shortcuts []WindowsShortcutConfig `yaml:"shortcuts" mapstructure:"shortcuts"` // 快捷方式列表
}
// WindowsShortcutConfig 表示Windows快捷方式配置
type WindowsShortcutConfig struct {
Name string `yaml:"name" mapstructure:"name"` // 快捷方式名称
Target string `yaml:"target" mapstructure:"target"` // 目标路径
Description string `yaml:"description" mapstructure:"description"` // 描述
Icon string `yaml:"icon" mapstructure:"icon"` // 图标路径
WorkingDir string `yaml:"workingDir" mapstructure:"workingDir"` // 工作目录
}
// LinuxConfig 表示Linux平台配置
type LinuxConfig struct {
Deb LinuxDebConfig `yaml:"deb" mapstructure:"deb"` // DEB配置
Rpm LinuxRpmConfig `yaml:"rpm" mapstructure:"rpm"` // RPM配置
}
// LinuxDebConfig 表示Linux DEB配置
type LinuxDebConfig struct {
Section string `yaml:"section" mapstructure:"section"` // 软件包分类
Priority string `yaml:"priority" mapstructure:"priority"` // 优先级
Architecture string `yaml:"architecture" mapstructure:"architecture"` // 架构
Depends []string `yaml:"depends" mapstructure:"depends"` // 依赖项列表
Recommends []string `yaml:"recommends" mapstructure:"recommends"` // 推荐安装项列表
Suggests []string `yaml:"suggests" mapstructure:"suggests"` // 建议安装项列表
Conflicts []string `yaml:"conflicts" mapstructure:"conflicts"` // 冲突项列表
Replaces []string `yaml:"replaces" mapstructure:"replaces"` // 替换项列表
}
// LinuxRpmConfig 表示Linux RPM配置
type LinuxRpmConfig struct {
Group string `yaml:"group" mapstructure:"group"` // 软件包组
Vendor string `yaml:"vendor" mapstructure:"vendor"` // 供应商
URL string `yaml:"url" mapstructure:"url"` // URL
Requires []string `yaml:"requires" mapstructure:"requires"` // 依赖项列表
Provides []string `yaml:"provides" mapstructure:"provides"` // 提供项列表
Conflicts []string `yaml:"conflicts" mapstructure:"conflicts"` // 冲突项列表
Obsoletes []string `yaml:"obsoletes" mapstructure:"obsoletes"` // 废弃项列表
AutoReqProv bool `yaml:"autoReqProv" mapstructure:"autoReqProv"` // 自动依赖检测
}
// LoadConfig 从指定路径加载配置文件
func LoadConfig(path string) (*Config, error) {
v := viper.New()
v.SetConfigFile(path)
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
var config Config
if err := v.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
return &config, nil
}
// SaveConfig 将配置保存到指定路径
func SaveConfig(config *Config, path string) error {
// 确保目录存在
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建目录失败: %w", err)
}
// 将配置转换为YAML
data, err := yaml.Marshal(config)
if err != nil {
return fmt.Errorf("序列化配置失败: %w", err)
}
// 写入文件
if err := os.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
return nil
}
// DefaultConfig 返回默认配置
func DefaultConfig() *Config {
return &Config{
Name: "my-application",
Version: "1.0.0",
Description: "My Application Description",
Author: "Author Name",
License: "MIT",
Build: BuildConfig{
OutputDir: "dist",
Targets: []string{"windows", "linux"},
Formats: []string{"msi", "deb", "rpm", "zip"},
},
Contents: ContentsConfig{
Files: []FileConfig{
{
Source: "bin/myapp",
Destination: "bin/myapp",
Mode: "0755",
},
{
Source: "README.md",
Destination: "doc/README.md",
Mode: "0644",
},
},
Directories: []DirectoryConfig{
{
Path: "logs",
Mode: "0755",
},
{
Path: "config",
Mode: "0755",
},
},
Scripts: []ScriptConfig{
{
Type: "postinstall",
Content: "#!/bin/sh\necho \"Installation completed successfully!\"",
},
},
},
Platforms: PlatformsConfig{
Windows: WindowsConfig{
Msi: WindowsMsiConfig{
UpgradeCode: "12345678-1234-1234-1234-123456789012",
Manufacturer: "My Company",
InstallDir: "MyApplication",
ProgramMenuDir: "MyApplication",
Shortcuts: []WindowsShortcutConfig{
{
Name: "My Application",
Target: "[INSTALLDIR]bin\\myapp.exe",
Description: "Launch My Application",
Icon: "[INSTALLDIR]icons\\app.ico",
},
},
},
},
Linux: LinuxConfig{
Deb: LinuxDebConfig{
Section: "utils",
Priority: "optional",
Architecture: "amd64",
Depends: []string{"libc6"},
},
Rpm: LinuxRpmConfig{
Group: "Applications/System",
Vendor: "My Company",
URL: "https://example.com",
Requires: []string{"glibc"},
AutoReqProv: true,
},
},
},
}
}

248
internal/config/validate.go Normal file
View File

@ -0,0 +1,248 @@
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// ValidationError 表示配置验证错误
type ValidationError struct {
Field string
Message string
}
// ValidationResult 表示配置验证结果
type ValidationResult struct {
Valid bool
Errors []ValidationError
}
// String 返回验证结果的字符串表示
func (vr ValidationResult) String() string {
if vr.Valid {
return "配置验证通过"
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("配置验证失败,共有 %d 个错误:\n", len(vr.Errors)))
for i, err := range vr.Errors {
sb.WriteString(fmt.Sprintf("%d. %s: %s\n", i+1, err.Field, err.Message))
}
return sb.String()
}
// Validate 验证配置是否有效
func (c *Config) Validate(strict bool) ValidationResult {
result := ValidationResult{
Valid: true,
Errors: []ValidationError{},
}
// 验证基本信息
if c.Name == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "name",
Message: "安装包名称不能为空",
})
}
if c.Version == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "version",
Message: "安装包版本不能为空",
})
}
// 验证构建设置
if len(c.Build.Targets) == 0 {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "build.targets",
Message: "至少需要指定一个目标平台",
})
}
if len(c.Build.Formats) == 0 {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "build.formats",
Message: "至少需要指定一个输出格式",
})
}
// 验证文件和目录
for i, file := range c.Contents.Files {
if file.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].source", i),
Message: "源文件路径不能为空",
})
} else if strict {
// 在严格模式下,验证源文件是否存在
if _, err := os.Stat(file.Source); os.IsNotExist(err) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].source", i),
Message: fmt.Sprintf("源文件 '%s' 不存在", file.Source),
})
}
}
if file.Destination == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].destination", i),
Message: "目标路径不能为空",
})
}
}
// 验证脚本
for i, script := range c.Contents.Scripts {
if script.Type == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].type", i),
Message: "脚本类型不能为空",
})
} else if !isValidScriptType(script.Type) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].type", i),
Message: fmt.Sprintf("无效的脚本类型: %s", script.Type),
})
}
if script.Content == "" && script.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d]", i),
Message: "脚本内容和源文件路径不能同时为空",
})
}
if script.Source != "" && strict {
// 在严格模式下,验证脚本源文件是否存在
if _, err := os.Stat(script.Source); os.IsNotExist(err) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].source", i),
Message: fmt.Sprintf("脚本源文件 '%s' 不存在", script.Source),
})
}
}
}
// 验证符号链接
for i, symlink := range c.Contents.Symlinks {
if symlink.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.symlinks[%d].source", i),
Message: "符号链接源路径不能为空",
})
}
if symlink.Destination == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.symlinks[%d].destination", i),
Message: "符号链接目标路径不能为空",
})
}
}
// 验证平台特定配置
for _, target := range c.Build.Targets {
switch target {
case "windows":
// 验证Windows MSI配置
if containsFormat(c.Build.Formats, "msi") {
if c.Platforms.Windows.Msi.UpgradeCode == "" && strict {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "platforms.windows.msi.upgradeCode",
Message: "MSI升级代码不能为空",
})
}
}
case "linux":
// 验证Linux DEB配置
if containsFormat(c.Build.Formats, "deb") {
// 可以添加更多DEB特定的验证
}
// 验证Linux RPM配置
if containsFormat(c.Build.Formats, "rpm") {
// 可以添加更多RPM特定的验证
}
}
}
return result
}
// 检查是否为有效的脚本类型
func isValidScriptType(scriptType string) bool {
validTypes := []string{
"preinstall",
"postinstall",
"preuninstall",
"postuninstall",
}
for _, validType := range validTypes {
if scriptType == validType {
return true
}
}
return false
}
// 检查格式列表是否包含指定格式
func containsFormat(formats []string, format string) bool {
for _, f := range formats {
if f == format {
return true
}
}
return false
}
// ValidateConfigFile 验证指定路径的配置文件
func ValidateConfigFile(path string, strict bool) (ValidationResult, error) {
config, err := LoadConfig(path)
if err != nil {
return ValidationResult{
Valid: false,
Errors: []ValidationError{
{
Field: "file",
Message: err.Error(),
},
},
}, err
}
// 设置工作目录为配置文件所在目录,以便相对路径的验证
if strict {
oldWd, err := os.Getwd()
if err != nil {
return ValidationResult{}, err
}
defer os.Chdir(oldWd)
configDir := filepath.Dir(path)
if err := os.Chdir(configDir); err != nil {
return ValidationResult{}, err
}
}
return config.Validate(strict), nil
}

134
internal/logger/logger.go Normal file
View File

@ -0,0 +1,134 @@
// Package logger 提供日志记录功能
package logger
import (
"io"
"os"
"github.com/sirupsen/logrus"
)
// Logger 是应用程序的日志记录器
var Logger = logrus.New()
// 日志级别常量
const (
DebugLevel = logrus.DebugLevel
InfoLevel = logrus.InfoLevel
WarnLevel = logrus.WarnLevel
ErrorLevel = logrus.ErrorLevel
FatalLevel = logrus.FatalLevel
PanicLevel = logrus.PanicLevel
)
// 初始化日志记录器
func init() {
// 设置默认输出为标准输出
Logger.SetOutput(os.Stdout)
// 设置默认格式为文本格式
Logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置默认日志级别为信息级别
Logger.SetLevel(logrus.InfoLevel)
}
// SetLevel 设置日志级别
func SetLevel(level logrus.Level) {
Logger.SetLevel(level)
}
// SetOutput 设置日志输出目标
func SetOutput(output io.Writer) {
Logger.SetOutput(output)
}
// SetFormatter 设置日志格式化器
func SetFormatter(formatter logrus.Formatter) {
Logger.SetFormatter(formatter)
}
// Debug 记录调试级别的日志
func Debug(args ...interface{}) {
Logger.Debug(args...)
}
// Debugf 记录格式化的调试级别日志
func Debugf(format string, args ...interface{}) {
Logger.Debugf(format, args...)
}
// Info 记录信息级别的日志
func Info(args ...interface{}) {
Logger.Info(args...)
}
// Infof 记录格式化的信息级别日志
func Infof(format string, args ...interface{}) {
Logger.Infof(format, args...)
}
// Warn 记录警告级别的日志
func Warn(args ...interface{}) {
Logger.Warn(args...)
}
// Warnf 记录格式化的警告级别日志
func Warnf(format string, args ...interface{}) {
Logger.Warnf(format, args...)
}
// Error 记录错误级别的日志
func Error(args ...interface{}) {
Logger.Error(args...)
}
// Errorf 记录格式化的错误级别日志
func Errorf(format string, args ...interface{}) {
Logger.Errorf(format, args...)
}
// Fatal 记录致命级别的日志,然后退出程序
func Fatal(args ...interface{}) {
Logger.Fatal(args...)
}
// Fatalf 记录格式化的致命级别日志,然后退出程序
func Fatalf(format string, args ...interface{}) {
Logger.Fatalf(format, args...)
}
// Panic 记录恐慌级别的日志,然后触发恐慌
func Panic(args ...interface{}) {
Logger.Panic(args...)
}
// Panicf 记录格式化的恐慌级别日志,然后触发恐慌
func Panicf(format string, args ...interface{}) {
Logger.Panicf(format, args...)
}
// WithField 返回带有指定字段的日志条目
func WithField(key string, value interface{}) *logrus.Entry {
return Logger.WithField(key, value)
}
// WithFields 返回带有指定字段的日志条目
func WithFields(fields logrus.Fields) *logrus.Entry {
return Logger.WithFields(fields)
}
// ConfigureLogger 根据命令行标志配置日志记录器
func ConfigureLogger(verbose bool, quiet bool) {
if verbose {
SetLevel(DebugLevel)
Debug("已启用详细日志模式")
} else if quiet {
SetLevel(ErrorLevel)
} else {
SetLevel(InfoLevel)
}
}

View File

@ -0,0 +1,27 @@
// Package version 提供版本信息
package version
import (
"fmt"
"runtime"
)
var (
// Version 是应用程序的版本
Version = "dev"
// BuildTime 是构建时间
BuildTime = "unknown"
// CommitHash 是Git提交哈希
CommitHash = "unknown"
)
// Info 返回格式化的版本信息
func Info() string {
return fmt.Sprintf("Installer Builder %s (%s) built at %s using %s",
Version, CommitHash, BuildTime, runtime.Version())
}
// ShortInfo 返回简短的版本信息
func ShortInfo() string {
return fmt.Sprintf("Installer Builder %s", Version)
}

View File

@ -28,7 +28,7 @@
```
2. 初始化Go模块
- 创建`go.mod`文件定义模块路径和Go版本
- 创建`go.mod`文件定义模块路径和Go版本,模块名定义为`git.kingecg.top/kingecg/installerbuilder`
- 初始化`go.sum`文件
3. 设置基本依赖: