Files
go2spec/spec.go

303 lines
9.6 KiB
Go

package main
import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version string,
pkgType packageType, dependencies []string, u *upstream) error {
f, err := os.Create(filepath.Join(dir, "", openRuyiSrc+".spec"))
if err != nil {
return err
}
defer f.Close()
description, err := getDescriptionForGopkg(gopkg)
if err != nil {
log.Printf("Could not determine description for %q: %v\n", gopkg, err)
description = "TODO: short description"
}
longdescription, err := getLongDescriptionForGopkg(gopkg)
if err != nil {
log.Printf("Could not determine long description for %q: %v\n", gopkg, err)
longdescription = "TODO: long description"
}
longdescription = convertLongDescriptionForRPM(longdescription)
license, err := getLicenseForGopkg(gopkg)
if err != nil {
log.Printf("Could not determine license for %q: %v\n", gopkg, err)
license = "TODO"
}
upstreamName := filepath.Base(gopkg)
if regexp.MustCompile(`^v\d+$`).MatchString(upstreamName) {
upstreamName = filepath.Base(filepath.Dir(gopkg))
}
owner, repo, err := findGitHubRepo(gopkg)
if err != nil {
owner = "TODO"
repo = "TODO"
}
// Write the spec file content
// SPDX header
fmt.Fprintf(f, "# SPDX-FileCopyrightText: (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS)\n")
fmt.Fprintf(f, "# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors\n")
fmt.Fprintf(f, "# SPDX-FileContributor: \n")
fmt.Fprintf(f, "#\n")
fmt.Fprintf(f, "# SPDX-License-Identifier: MulanPSL-2.0\n")
fmt.Fprintf(f, "\n")
// Macros
fmt.Fprintf(f, "%%define _name %s\n", upstreamName)
fmt.Fprintf(f, "%%define go_import_path %s\n", gopkg)
fmt.Fprintf(f, "\n")
// Header
fmt.Fprintf(f, "Name: %s\n", openRuyiSrc)
fmt.Fprintf(f, "Version: %s\n", version)
fmt.Fprintf(f, "Release: %%autorelease\n")
fmt.Fprintf(f, "Summary: %s\n", description)
fmt.Fprintf(f, "License: %s\n", license)
fmt.Fprintf(f, "URL: https://github.com/%s/%s\n", owner, repo)
fmt.Fprintf(f, "#!RemoteAsset\n")
fmt.Fprintf(f, "Source0: https://github.com/%s/%s/archive/v%%{version}.tar.gz#/%%{_name}-%%{version}.tar.gz\n", owner, repo)
switch pkgType {
case typeLibrary:
fmt.Fprintf(f, "BuildArch: noarch\n")
fmt.Fprintf(f, "BuildSystem: golangmodules\n")
fmt.Fprintf(f, "\n")
case typeProgram, typeLibraryProgram, typeProgramLibrary:
fmt.Fprintf(f, "\n")
}
fmt.Fprintf(f, "BuildRequires: go\n")
fmt.Fprintf(f, "BuildRequires: go-rpm-macros\n")
// And other BuildRequires from dependencies
rpmDeps := convertDependenciesToRPM(dependencies)
sort.Strings(rpmDeps)
for _, dep := range rpmDeps {
fmt.Fprintf(f, "BuildRequires: %s\n", dep)
}
// For different package types, write different sections
switch pkgType {
case typeLibrary:
writeRPMLibraryPackage(f, gopkg, openRuyiLib, longdescription, rpmDeps)
case typeProgram:
log.Printf("Nothing to do for program package.\n")
// TODO: what can this be used for? ExclusiveArch %%{go_arches}?
// writeRPMProgramPackage(f, gopkg, openRuyiProgram, longdescription)
case typeLibraryProgram:
writeRPMLibraryPackage(f, gopkg, openRuyiLib, longdescription, rpmDeps)
writeRPMProgramSubpackage(f, gopkg, openRuyiProgram, openRuyiSrc, description)
case typeProgramLibrary:
//writeRPMProgramPackage(f, gopkg, openRuyiProgram, longdescription)
writeRPMLibrarySubpackage(f, gopkg, openRuyiLib, openRuyiSrc, longdescription, rpmDeps)
default:
log.Fatalf("Invalid pkgType %d in writeRPMSpec(), aborting", pkgType)
}
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "%%description\n")
fmt.Fprintf(f, "%s\n", longdescription)
fmt.Fprintf(f, "\n")
// %files
writeRPMFilesSection(f, openRuyiSrc, openRuyiLib, openRuyiProgram, pkgType)
// %changelog
fmt.Fprintf(f, "%%changelog\n")
fmt.Fprintf(f, "%%{?autochangelog}\n\n")
return nil
}
// For library package
func writeRPMLibraryPackage(f *os.File, gopkg, openRuyiLib, longdesc string, deps []string) {
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "Provides: go(%s) = %%{version}\n", gopkg)
// 库包的运行时依赖
if len(deps) > 0 {
for _, dep := range deps {
fmt.Fprintf(f, "Requires: %s\n", dep)
}
fmt.Fprintf(f, "\n")
}
}
// For library subpackage
func writeRPMLibrarySubpackage(f *os.File, gopkg, openRuyiLib, openRuyiSrc, longdesc string, deps []string) {
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "%%package -n %s\n", openRuyiLib)
fmt.Fprintf(f, "Summary: Development files of %s\n", filepath.Base(gopkg))
fmt.Fprintf(f, "Provides: go(%s) = %%{version}\n", gopkg)
fmt.Fprintf(f, "BuildArch: noarch\n")
for _, dep := range deps {
fmt.Fprintf(f, "Requires: %s\n", dep)
}
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "%%description -n %s\n", openRuyiLib)
fmt.Fprintf(f, "%s\n", longdesc)
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "This package provides the Go source files of %s for development.\n")
}
// For program subpackage
func writeRPMProgramSubpackage(f *os.File, gopkg, openRuyiProgram, openRuyiSrc, description string) {
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "%%package -n %s\n", openRuyiProgram)
fmt.Fprintf(f, "Summary: Executable of %s\n", filepath.Base(gopkg))
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "%%description -n %s\n", openRuyiProgram)
fmt.Fprintf(f, "%s\n", description)
fmt.Fprintf(f, "\n")
fmt.Fprintf(f, "This package contains the %s executable.\n", filepath.Base(gopkg))
}
func writeRPMFilesSection(f *os.File, openRuyiSrc, openRuyiLib, openRuyiProgram string, pkgType packageType) {
switch pkgType {
case typeLibrary:
fmt.Fprintf(f, "%%files\n")
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%doc README*\n")
fmt.Fprintf(f, "%%{go_sys_gopath}/%%{go_import_path}\n")
fmt.Fprintf(f, "\n")
case typeProgram:
fmt.Fprintf(f, "%%files\n")
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%doc README*\n")
fmt.Fprintf(f, "%%{_bindir}/%%{_name}\n")
fmt.Fprintf(f, "\n")
case typeLibraryProgram:
// 库包文件(主包)
fmt.Fprintf(f, "%%files\n")
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%doc README*\n")
fmt.Fprintf(f, "%%{go_sys_gopath}/%%{go_import_path}\n")
fmt.Fprintf(f, "\n")
// 程序子包文件
fmt.Fprintf(f, "%%files -n %s\n", openRuyiProgram)
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%{_bindir}/%%{_name}\n")
fmt.Fprintf(f, "\n")
case typeProgramLibrary:
// 程序主包文件
fmt.Fprintf(f, "%%files\n")
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%doc README*\n")
fmt.Fprintf(f, "%%{_bindir}/%%{_name}\n")
fmt.Fprintf(f, "\n")
// 库子包文件
fmt.Fprintf(f, "%%files -n %s\n", openRuyiLib)
fmt.Fprintf(f, "%%license LICENSE*\n")
fmt.Fprintf(f, "%%{go_sys_gopath}/%%{go_import_path}\n")
fmt.Fprintf(f, "\n")
}
}
func convertLongDescriptionForRPM(openRuyiSrc string) string {
// 移除 Debian 特有的格式
lines := strings.Split(openRuyiSrc, "\n")
var result []string
for _, line := range lines {
// 移除行首空格
line = strings.TrimPrefix(line, " ")
// 将 "." 单独行转换为空行
if line == "." {
line = ""
}
result = append(result, line)
}
return strings.Join(result, "\n")
}
// Sync with shortHostName() from pack.go
func getKnownHostsReverse() map[string]string {
return map[string]string{
"bazil": "bazil.org",
"bitbucket": "bitbucket.org",
"blitiri": "blitiri.com.ar",
"googlecloud": "cloud.google.com",
"googlecode": "code.google.com",
"codeberg": "codeberg.org",
"filippo": "filippo.io",
"fortio": "fortio.org",
"fyne": "fyne.io",
"sourcehut": "git.sr.ht",
"github": "github.com",
"gitlab": "gitlab.com",
"bugst": "go.bug.st",
"cypherpunks": "go.cypherpunks.ru",
"mongodb": "go.mongodb.org",
"opentelemetry": "go.opentelemetry.io",
"step": "go.step.sm",
"uber": "go.uber.org",
"go4": "go4.org",
"gocloud": "gocloud.dev",
"golang": "golang.org",
"google": "google.golang.org",
"gopkg": "gopkg.in",
"honnef": "honnef. co",
"howett": "howett.net",
"k8s": "k8s.io",
"modernc": "modernc.org",
"pault": "pault.ag",
"rsc": "rsc.io",
"debian": "salsa.debian.org",
"k8s-sigs": "sigs.k8s.io",
"sslmate": "software.sslmate.com",
"zgoat": "zgo.at",
}
}
func convertDependenciesToRPM(debDeps []string) []string {
knownHostsReverse := getKnownHostsReverse()
var rpmDeps []string
for _, dep := range debDeps {
// 转换 golang-xxx-dev 为 go(xxx)
//if strings.HasPrefix(dep, "go-") && strings.HasSuffix(dep, "-devel") {
if strings.HasPrefix(dep, "go-") {
// golang-github-foo-bar-dev -> go(github.com/foo/bar)
trimmed := strings.TrimPrefix(dep, "go-")
//trimmed = strings.TrimSuffix(trimmed, "-devel")
// 尝试还原 Go 包路径
parts := strings.Split(trimmed, "-")
if len(parts) >= 2 {
shortHost := parts[0]
// 使用 knownHosts 反向映射查找完整主机名
var fullHost string
if fqdn, ok := knownHostsReverse[shortHost]; ok {
fullHost = fqdn
} else {
// 未知主机,保持原样(可能是自定义域名)
fullHost = shortHost
log.Printf("WARNING: Unknown host shortname %q in dependency %q\n", shortHost, dep)
}
importPath := fullHost + "/" + strings.Join(parts[1:], "/")
rpmDeps = append(rpmDeps, fmt.Sprintf("go(%s)", importPath))
} else {
rpmDeps = append(rpmDeps, fmt.Sprintf("go(%s)", trimmed))
}
} else {
rpmDeps = append(rpmDeps, dep)
}
}
return rpmDeps
}