installerbuilder/internal/builder/linux/rpm.go

361 lines
9.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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