installerbuilder/internal/builder/builder.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
}
}