361 lines
9.1 KiB
Go
361 lines
9.1 KiB
Go
// 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
|
||
}
|