219 lines
5.1 KiB
Go
219 lines
5.1 KiB
Go
// 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
|
|
}
|
|
}
|