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