// 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 } }