forked from misaka00251/go2spec
refactor: migrate metadata discovery to pkgsite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+4
-3
@@ -24,7 +24,6 @@ var (
|
||||
|
||||
// uversionPrereleaseRegexp checks for upstream pre-release
|
||||
// so that '-' can be replaced with '~' in pkgVersionFromGit.
|
||||
// To be kept in sync with the regexp portion of uversionmanglePattern in spec.go
|
||||
uversionPrereleaseRegexp = regexp.MustCompile(`(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$`)
|
||||
)
|
||||
|
||||
@@ -42,8 +41,10 @@ func pkgVersionFromGit(gitdir string, u *upstream, preferredRev string, forcePre
|
||||
var cmd *exec.Cmd // the temporary shell commands we execute
|
||||
|
||||
// If the user specifies a valid tag as the preferred revision, that tag should be used without additional heuristics.
|
||||
if u.rr != nil {
|
||||
if out, err := u.rr.VCS.Tags(gitdir); err == nil && slices.Contains(out, preferredRev) {
|
||||
if preferredRev != "" {
|
||||
cmd = exec.Command("git", "tag", "--list", preferredRev)
|
||||
cmd.Dir = gitdir
|
||||
if out, err := cmd.Output(); err == nil && slices.Contains(strings.Fields(string(out)), preferredRev) {
|
||||
latestTag = preferredRev
|
||||
}
|
||||
}
|
||||
|
||||
+15
-21
@@ -13,8 +13,7 @@ import (
|
||||
//go:embed description.json
|
||||
var descriptionJSONBytes []byte
|
||||
|
||||
// reformatForControl reformats the wrapped description
|
||||
// to conform to Debian’s control format.
|
||||
// reformatForControl reformats wrapped text for the RPM spec's %description.
|
||||
func reformatForControl(raw string) string {
|
||||
output := ""
|
||||
next_prefix := ""
|
||||
@@ -47,8 +46,8 @@ func reformatForControl(raw string) string {
|
||||
return output
|
||||
}
|
||||
|
||||
// markdownToLongDescription converts Markdown to plain text
|
||||
// and reformat it for expanded description in debian/control.
|
||||
// markdownToLongDescription converts Markdown to plain text for the RPM spec's
|
||||
// %description section.
|
||||
func markdownToLongDescription(markdown string) (string, error) {
|
||||
r, _ := glamour.NewTermRenderer(
|
||||
glamour.WithStylesFromJSONBytes(descriptionJSONBytes),
|
||||
@@ -63,23 +62,17 @@ func markdownToLongDescription(markdown string) (string, error) {
|
||||
return reformatForControl(out), nil
|
||||
}
|
||||
|
||||
// getDescriptionForGopkg reads from README.md (or equivalent) from GitHub,
|
||||
// intended for extended description in debian/control.
|
||||
// getLongDescriptionForGopkg reads README.md (or equivalent) from pkg.go.dev,
|
||||
// intended for the RPM spec's %description section.
|
||||
func getLongDescriptionForGopkg(gopkg string) (string, error) {
|
||||
owner, repo, err := findGitHubRepo(gopkg)
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find github repo: %w", err)
|
||||
return "", fmt.Errorf("get pkgsite metadata: %w", err)
|
||||
}
|
||||
|
||||
rr, _, err := gitHub.Repositories.GetReadme(context.TODO(), owner, repo, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get readme: %w", err)
|
||||
}
|
||||
|
||||
content, err := rr.GetContent()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get content: %w", err)
|
||||
if info.Module.Readme == nil || strings.TrimSpace(info.Module.Readme.Contents) == "" {
|
||||
return "", fmt.Errorf("pkgsite module %q has no README", info.Module.Path)
|
||||
}
|
||||
content := info.Module.Readme.Contents
|
||||
|
||||
// Supported filename suffixes are from
|
||||
// https://github.com/github/markup/blob/master/README.md
|
||||
@@ -88,10 +81,11 @@ func getLongDescriptionForGopkg(gopkg string) (string, error) {
|
||||
// fairly involved, but it’d be the most correct solution to the problem at
|
||||
// hand. Our current code just knows markdown, which is good enough since
|
||||
// most (Go?) projects in fact use markdown for their README files.
|
||||
if !strings.HasSuffix(rr.GetName(), "md") &&
|
||||
!strings.HasSuffix(rr.GetName(), "markdown") &&
|
||||
!strings.HasSuffix(rr.GetName(), "mdown") &&
|
||||
!strings.HasSuffix(rr.GetName(), "mkdn") {
|
||||
readmeName := strings.ToLower(info.Module.Readme.Filepath)
|
||||
if !strings.HasSuffix(readmeName, "md") &&
|
||||
!strings.HasSuffix(readmeName, "markdown") &&
|
||||
!strings.HasSuffix(readmeName, "mdown") &&
|
||||
!strings.HasSuffix(readmeName, "mkdn") {
|
||||
return reformatForControl(content), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,9 @@ go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/google/go-github/v60 v60.0.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/sandrolain/httpcache v1.4.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/tools/go/vcs v0.1.0-deprecated
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,7 +20,6 @@ require (
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
|
||||
@@ -28,13 +28,6 @@ github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQ
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8=
|
||||
github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
@@ -76,6 +69,3 @@ golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools/go/vcs v0.1.0-deprecated h1:cOIJqWBl99H1dH5LWizPa+0ImeeJq3t3cJjaeOWUAL4=
|
||||
golang.org/x/tools/go/vcs v0.1.0-deprecated/go.mod h1:zUrvATBAvEI9535oC0yWYsLsHIV4Z7g63sNPVMtuBy8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -3,26 +3,11 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v60/github"
|
||||
"github.com/sandrolain/httpcache"
|
||||
)
|
||||
|
||||
var (
|
||||
gitHub *github.Client
|
||||
)
|
||||
|
||||
// TokenTransport implements http.RoundTripper for Bearer token authentication
|
||||
type TokenTransport struct {
|
||||
Token string
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Add("Authorization", "Bearer "+t.Token)
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
helpText := `go2spec - A tool to package Go modules into RPM spec files.
|
||||
|
||||
@@ -41,28 +26,10 @@ If there are no commands provided, the tool will default to executing the 'pack'
|
||||
}
|
||||
|
||||
func main() {
|
||||
token := os.Getenv("GITHUB_TOKEN")
|
||||
|
||||
var client *http.Client
|
||||
if token != "" {
|
||||
// Use token authentication for better rate limits
|
||||
client = &http.Client{
|
||||
Transport: &TokenTransport{
|
||||
Token: token,
|
||||
Transport: httpcache.NewMemoryCacheTransport(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Fallback to basic auth if token is not provided
|
||||
transport := github.BasicAuthTransport{
|
||||
Username: os.Getenv("GITHUB_USERNAME"),
|
||||
Password: os.Getenv("GITHUB_PASSWORD"),
|
||||
OTP: os.Getenv("GITHUB_OTP"),
|
||||
Transport: httpcache.NewMemoryCacheTransport(),
|
||||
}
|
||||
client = transport.Client()
|
||||
pkgsiteHTTPClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: httpcache.NewMemoryCacheTransport(),
|
||||
}
|
||||
gitHub = github.NewClient(client)
|
||||
|
||||
args := os.Args[1:]
|
||||
|
||||
|
||||
+109
-98
@@ -3,128 +3,139 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"html"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// To update, use:
|
||||
// curl -s https://api.github.com/licenses | jq '.[].key'
|
||||
var githubLicenseToSPDXLicense = map[string]string{
|
||||
//"agpl-3.0"
|
||||
"apache-2.0": "Apache-2.0",
|
||||
"artistic-2.0": "Artistic-2.0",
|
||||
"bsd-2-clause": "BSD-2-Clause",
|
||||
"bsd-3-clause": "BSD-3-Clause",
|
||||
"cc0-1.0": "CC0-1.0",
|
||||
//"epl-1.0" (eclipse public license)
|
||||
"gpl-2.0": "GPL-2.0-only",
|
||||
"gpl-3.0": "GPL-3.0-only",
|
||||
"isc": "ISC",
|
||||
"lgpl-2.1": "LGPL-2.1-only",
|
||||
"lgpl-3.0": "LGPL-3.0-only",
|
||||
"mit": "MIT",
|
||||
"mpl-2.0": "MPL-2.0",
|
||||
//"unlicense"
|
||||
func getRepoURLForGopkg(gopkg string) (string, error) {
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
repoURL := strings.TrimSpace(info.Module.RepoURL)
|
||||
if repoURL == "" {
|
||||
return "", fmt.Errorf("pkgsite module %q has no repository URL", info.Module.Path)
|
||||
}
|
||||
return gitCloneURLFromRepoURL(repoURL), nil
|
||||
}
|
||||
|
||||
var githubRegexp = regexp.MustCompile(`github\.com/([^/]+/[^/]+)`)
|
||||
var (
|
||||
htmlTagRegexp = regexp.MustCompile(`<[^>]*>`)
|
||||
markdownImageRegex = regexp.MustCompile(`!\[[^\]]*\]\([^)]+\)`)
|
||||
markdownLinkRegex = regexp.MustCompile(`\[([^\]]+)\]\([^)]+\)`)
|
||||
packagePrefixRegex = regexp.MustCompile(`^Package\s+\S+\s+`)
|
||||
)
|
||||
|
||||
func findGitHubOwnerRepo(gopkg string) (string, error) {
|
||||
if strings.HasPrefix(gopkg, "github.com/") {
|
||||
return strings.TrimPrefix(gopkg, "github.com/"), nil
|
||||
// cleanSummaryCandidate turns a godoc synopsis or README line into an
|
||||
// RPM-style Summary by stripping markup, keeping the first sentence, dropping
|
||||
// the leading "Package foo" convention, and capitalizing the result.
|
||||
func cleanSummaryCandidate(summary string) string {
|
||||
summary = html.UnescapeString(strings.TrimSpace(summary))
|
||||
summary = markdownImageRegex.ReplaceAllString(summary, "")
|
||||
summary = markdownLinkRegex.ReplaceAllString(summary, "$1")
|
||||
summary = htmlTagRegexp.ReplaceAllString(summary, " ")
|
||||
summary = strings.ReplaceAll(summary, "`", "")
|
||||
summary = strings.Join(strings.Fields(summary), " ")
|
||||
summary = strings.Trim(summary, " \t\n\r#*-_")
|
||||
if end := strings.Index(summary, ". "); end >= 0 {
|
||||
summary = summary[:end]
|
||||
}
|
||||
resp, err := http.Get("https://" + gopkg + "?go-get=1")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("HTTP get: %w", err)
|
||||
summary = packagePrefixRegex.ReplaceAllString(summary, "")
|
||||
if summary != "" && summary[0] >= 'a' && summary[0] <= 'z' {
|
||||
summary = string(summary[0]-('a'-'A')) + summary[1:]
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
z := html.NewTokenizer(resp.Body)
|
||||
for {
|
||||
tt := z.Next()
|
||||
if tt == html.ErrorToken {
|
||||
return "", fmt.Errorf("%q is not on GitHub", gopkg)
|
||||
}
|
||||
token := z.Token()
|
||||
if token.Data != "meta" {
|
||||
if summary == "" ||
|
||||
strings.HasPrefix(summary, "[!") ||
|
||||
strings.HasPrefix(summary, "![") ||
|
||||
strings.HasPrefix(summary, "<!--") ||
|
||||
strings.ContainsAny(summary, "<>") {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSuffix(summary, ".")
|
||||
}
|
||||
|
||||
// summaryFromReadme returns the first prose line of a README, skipping code
|
||||
// fences, headings, HTML comments, badges, admonitions, and images.
|
||||
func summaryFromReadme(markdown string) string {
|
||||
inFence := false
|
||||
lines := strings.Split(markdown, "\n")
|
||||
for i, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "```") {
|
||||
inFence = !inFence
|
||||
continue
|
||||
}
|
||||
var meta struct {
|
||||
name, content string
|
||||
}
|
||||
for _, attr := range token.Attr {
|
||||
if attr.Key == "name" {
|
||||
meta.name = attr.Val
|
||||
}
|
||||
if attr.Key == "content" {
|
||||
meta.content = attr.Val
|
||||
if i+1 < len(lines) {
|
||||
next := strings.TrimSpace(lines[i+1])
|
||||
if next != "" && strings.Trim(next, "=-") == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
match := func(name string, length int) string {
|
||||
if f := strings.Fields(meta.content); meta.name == name && len(f) == length {
|
||||
if f[0] != gopkg {
|
||||
return ""
|
||||
}
|
||||
if repoMatch := githubRegexp.FindStringSubmatch(f[2]); repoMatch != nil {
|
||||
return strings.TrimSuffix(repoMatch[1], ".git")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
if inFence ||
|
||||
line == "" ||
|
||||
strings.HasPrefix(line, "#") ||
|
||||
strings.HasPrefix(line, "[!") ||
|
||||
strings.HasPrefix(line, "![") ||
|
||||
strings.HasPrefix(line, "<!--") ||
|
||||
strings.HasPrefix(line, "<p align=") ||
|
||||
strings.HasPrefix(line, "---") ||
|
||||
strings.Trim(line, "=-") == "" {
|
||||
continue
|
||||
}
|
||||
if repo := match("go-import", 3); repo != "" {
|
||||
return repo, nil
|
||||
}
|
||||
if repo := match("go-source", 4); repo != "" {
|
||||
return repo, nil
|
||||
if summary := cleanSummaryCandidate(line); summary != "" {
|
||||
return summary
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func findGitHubRepo(gopkg string) (owner string, repo string, _ error) {
|
||||
ownerrepo, err := findGitHubOwnerRepo(gopkg)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("find GitHub owner repo: %w", err)
|
||||
func unusableSummary(summary, gopkg string, info *pkgsiteInfo) bool {
|
||||
if summary == "" {
|
||||
return true
|
||||
}
|
||||
parts := strings.Split(ownerrepo, "/")
|
||||
if got, want := len(parts), 2; got != want {
|
||||
return "", "", fmt.Errorf("invalid GitHub repo: %q does not follow owner/repo", repo)
|
||||
}
|
||||
return parts[0], parts[1], nil
|
||||
base := path.Base(gopkg)
|
||||
moduleBase := path.Base(info.Module.Path)
|
||||
return strings.EqualFold(summary, base) ||
|
||||
strings.EqualFold(summary, moduleBase) ||
|
||||
strings.EqualFold(summary, info.Package.Name)
|
||||
}
|
||||
|
||||
func getLicenseForGopkg(gopkg string) (string, error) {
|
||||
owner, repo, err := findGitHubRepo(gopkg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find GitHub repo: %w", err)
|
||||
}
|
||||
|
||||
rl, _, err := gitHub.Repositories.License(context.TODO(), owner, repo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get license for Go package: %w", err)
|
||||
}
|
||||
|
||||
if license, ok := githubLicenseToSPDXLicense[rl.GetLicense().GetKey()]; ok {
|
||||
return license, nil
|
||||
}
|
||||
|
||||
return "TODO", nil
|
||||
}
|
||||
|
||||
// getDescriptionForGopkg gets the package description from GitHub,
|
||||
// intended for the synopsis or the short description in debian/control.
|
||||
func getDescriptionForGopkg(gopkg string) (string, error) {
|
||||
owner, repo, err := findGitHubRepo(gopkg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find GitHub repo: %w", err)
|
||||
}
|
||||
|
||||
rr, _, err := gitHub.Repositories.Get(context.TODO(), owner, repo)
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(rr.GetDescription()), nil
|
||||
licenses := info.Module.Licenses
|
||||
if len(licenses) == 0 {
|
||||
licenses = info.Package.Licenses
|
||||
}
|
||||
|
||||
return pkgsiteLicenseExpression(licenses), nil
|
||||
}
|
||||
|
||||
// getDescriptionForGopkg gets the package synopsis from pkg.go.dev,
|
||||
// intended for the summary in the RPM spec.
|
||||
func getDescriptionForGopkg(gopkg string) (string, error) {
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
description := cleanSummaryCandidate(info.Package.Synopsis)
|
||||
if unusableSummary(description, gopkg, info) {
|
||||
description = ""
|
||||
}
|
||||
if description == "" && info.Module.Readme != nil {
|
||||
description = summaryFromReadme(info.Module.Readme.Contents)
|
||||
if unusableSummary(description, gopkg, info) {
|
||||
description = ""
|
||||
}
|
||||
}
|
||||
if description == "" {
|
||||
return "", fmt.Errorf("no usable synopsis for %q", gopkg)
|
||||
}
|
||||
return description, nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -12,11 +14,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
"golang.org/x/tools/go/vcs"
|
||||
)
|
||||
|
||||
type packageType int
|
||||
@@ -31,7 +31,8 @@ const (
|
||||
|
||||
// upstream describes the upstream repo we are about to package.
|
||||
type upstream struct {
|
||||
rr *vcs.RepoRoot
|
||||
repoURL string
|
||||
modulePath string
|
||||
tarPath string // path to the downloaded or generated orig tarball tempfile
|
||||
compression string // compression method, either "gz" or "xz"
|
||||
version string // upstream version number, e.g. 0.0~git20180204.1d24609
|
||||
@@ -39,6 +40,7 @@ type upstream struct {
|
||||
commitIsh string // commit-ish corresponding to upstream version to be packaged
|
||||
remote string // git remote, set to short hostname if upstream git history is included
|
||||
firstMain string // import path of the first main package within repo, if any
|
||||
packageName string // package name of the requested package from pkg.go.dev
|
||||
vendorDirs []string // all vendor sub directories, relative to the repo directory
|
||||
repoDeps []string // all non-stdlib imports needed for build or tests
|
||||
repoRunDeps []string // non-stdlib imports needed by normal builds, excluding test-only imports
|
||||
@@ -122,29 +124,92 @@ func downloadFile(filename, url string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get downloads the specified Go package into the provided GOPATH,
|
||||
// get downloads the source module into the provided GOPATH,
|
||||
// checking out the specified revision if non-empty.
|
||||
func (u *upstream) get(gopath, repo, rev string) error {
|
||||
func (u *upstream) get(gopath, sourceRepo, requestedPath, rev string) error {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go progressSize("go get", filepath.Join(gopath, "src"), done)
|
||||
go progressSize("git clone", filepath.Join(gopath, "src"), done)
|
||||
|
||||
rr, err := vcs.RepoRootForImportPath(repo, false)
|
||||
info, err := getPkgsiteInfo(context.TODO(), requestedPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get repo root: %w", err)
|
||||
return fmt.Errorf("get pkgsite metadata: %w", err)
|
||||
}
|
||||
u.rr = rr
|
||||
dir := filepath.Join(gopath, "src", rr.Root)
|
||||
u.packageName = info.Package.Name
|
||||
u.modulePath = info.Module.Path
|
||||
u.repoURL = gitCloneURLFromRepoURL(info.Module.RepoURL)
|
||||
if u.repoURL == "" {
|
||||
return fmt.Errorf("pkgsite module %q has no repository URL", info.Module.Path)
|
||||
}
|
||||
|
||||
dir := filepath.Join(gopath, "src", sourceRepo)
|
||||
if err := os.MkdirAll(filepath.Dir(dir), 0755); err != nil {
|
||||
return fmt.Errorf("mkdir clone parent: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "clone", u.repoURL, dir)
|
||||
cmd.Env = passthroughEnv()
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Println("get: Running", cmd)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("git clone: %w", err)
|
||||
}
|
||||
|
||||
if rev != "" {
|
||||
// Run "git clone {repo} {dir}" and "git checkout {tag}"
|
||||
return rr.VCS.CreateAtRev(dir, rr.Repo, rev)
|
||||
cmd = exec.Command("git", "-c", "advice.detachedHead=false", "checkout", rev)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = passthroughEnv()
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Println("get: Running", cmd, "in", cmd.Dir)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("git checkout %q: %w", rev, err)
|
||||
}
|
||||
}
|
||||
// Run "git clone {repo} {dir}" (or the equivalent command for hg, svn, bzr)
|
||||
return rr.VCS.Create(dir, rr.Repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *upstream) tarballUrl() (string, error) {
|
||||
repo := strings.TrimSuffix(u.rr.Repo, ".git")
|
||||
func guessedPackageType(u *upstream) packageType {
|
||||
if u.packageName == "main" || (u.packageName == "" && u.firstMain != "") {
|
||||
return typeProgram
|
||||
}
|
||||
return typeLibrary
|
||||
}
|
||||
|
||||
// sourceImportPathForPackage returns the module root used for source checkout
|
||||
// and tarball generation, while callers keep the requested import path for
|
||||
// naming and go_import_path.
|
||||
func sourceImportPathForPackage(requestedPath string, info *pkgsiteInfo) string {
|
||||
if info != nil && info.Package.ModulePath != "" {
|
||||
return info.Package.ModulePath
|
||||
}
|
||||
if info != nil && info.Module.Path != "" {
|
||||
return info.Module.Path
|
||||
}
|
||||
return requestedPath
|
||||
}
|
||||
|
||||
// gitCloneURLFromRepoURL converts repository URLs returned by pkg.go.dev into
|
||||
// something git clone understands. pkg.go.dev reports golang.org/x/* modules
|
||||
// with a cs.opensource.google browser URL, so rewrite those to go.googlesource.com.
|
||||
func gitCloneURLFromRepoURL(repoURL string) string {
|
||||
repoURL = strings.TrimSuffix(strings.TrimSpace(repoURL), ".git")
|
||||
u, err := url.Parse(repoURL)
|
||||
if err != nil || u.Host != "cs.opensource.google" {
|
||||
return repoURL
|
||||
}
|
||||
|
||||
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
if len(parts) == 3 && parts[0] == "go" && parts[1] == "x" {
|
||||
return "https://go.googlesource.com/" + parts[2]
|
||||
}
|
||||
return repoURL
|
||||
}
|
||||
|
||||
func (u *upstream) tarballURLForRef(ref, compression string) (string, error) {
|
||||
repo := strings.TrimSuffix(u.repoURL, ".git")
|
||||
if repo == "" {
|
||||
return "", fmt.Errorf("repository URL is empty")
|
||||
}
|
||||
repoU, err := url.Parse(repo)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse URL: %w", err)
|
||||
@@ -153,26 +218,138 @@ func (u *upstream) tarballUrl() (string, error) {
|
||||
switch repoU.Host {
|
||||
case "github.com":
|
||||
return fmt.Sprintf("%s/archive/%s.tar.%s",
|
||||
repo, u.tag, u.compression), nil
|
||||
repo, ref, compression), nil
|
||||
case "gitlab.com", "salsa.debian.org":
|
||||
parts := strings.Split(repoU.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
return "", fmt.Errorf("incomplete repo URL: %s", u.rr.Repo)
|
||||
return "", fmt.Errorf("incomplete repo URL: %s", u.repoURL)
|
||||
}
|
||||
project := parts[2]
|
||||
project := strings.TrimSuffix(parts[len(parts)-1], ".git")
|
||||
return fmt.Sprintf("%s/-/archive/%s/%s-%s.tar.%s",
|
||||
repo, u.tag, project, u.tag, u.compression), nil
|
||||
repo, ref, project, ref, compression), nil
|
||||
case "git.sr.ht":
|
||||
return fmt.Sprintf("%s/archive/%s.tar.%s",
|
||||
repo, u.tag, u.compression), nil
|
||||
repo, ref, compression), nil
|
||||
case "codeberg.org":
|
||||
return fmt.Sprintf("%s/archive/%s.tar.%s",
|
||||
repo, u.tag, u.compression), nil
|
||||
repo, ref, compression), nil
|
||||
default:
|
||||
return "", errUnsupportedHoster
|
||||
}
|
||||
}
|
||||
|
||||
func (u *upstream) tarballUrl() (string, error) {
|
||||
return u.tarballURLForRef(u.tag, u.compression)
|
||||
}
|
||||
|
||||
func moduleProxyEscapedPath(modulePath string) string {
|
||||
parts := strings.Split(strings.Trim(modulePath, "/"), "/")
|
||||
for i, part := range parts {
|
||||
parts[i] = moduleProxyEscapeString(part)
|
||||
}
|
||||
return strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
// moduleProxyEscapeString applies the case-escape required by the Go module
|
||||
// proxy protocol: uppercase letters become "!" followed by lowercase.
|
||||
// See https://go.dev/ref/mod#goproxy-protocol.
|
||||
func moduleProxyEscapeString(s string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
if r >= 'A' && r <= 'Z' {
|
||||
b.WriteByte('!')
|
||||
b.WriteRune(r + ('a' - 'A'))
|
||||
continue
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
return strings.ReplaceAll(url.PathEscape(b.String()), "%21", "!")
|
||||
}
|
||||
|
||||
// moduleProxyVersionForSpec leaves RPM macros unchanged and otherwise applies
|
||||
// module proxy escaping so versions like v1.0.0-RC1 become v1.0.0-!r!c1.
|
||||
func moduleProxyVersionForSpec(version string) string {
|
||||
if strings.Contains(version, "%{") {
|
||||
return version
|
||||
}
|
||||
return moduleProxyEscapeString(version)
|
||||
}
|
||||
|
||||
func repoRootImportPath(repoURL string) string {
|
||||
u, err := url.Parse(strings.TrimSuffix(repoURL, ".git"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||
switch u.Host {
|
||||
case "github.com", "gitlab.com", "codeberg.org", "salsa.debian.org":
|
||||
if len(parts) >= 2 {
|
||||
return u.Host + "/" + strings.Join(parts[:2], "/")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// moduleUsesRepoSubdir reports whether modulePath lives in a repository
|
||||
// subdirectory. A trailing /vN semantic import version suffix is treated as
|
||||
// part of the repo-root module path, not as a subdirectory.
|
||||
func moduleUsesRepoSubdir(modulePath, repoURL string) bool {
|
||||
root := repoRootImportPath(repoURL)
|
||||
if root == "" || !strings.HasPrefix(modulePath, root+"/") {
|
||||
return false
|
||||
}
|
||||
rest := strings.TrimPrefix(modulePath, root+"/")
|
||||
return !regexp.MustCompile(`^v\d+$`).MatchString(rest)
|
||||
}
|
||||
|
||||
// sourceRefForSpec picks the git ref for Source0, preferring RPM macros when
|
||||
// the upstream version maps cleanly to %{commit_id} or %{version}.
|
||||
func (u *upstream) sourceRefForSpec() (string, error) {
|
||||
ref := u.tag
|
||||
if strings.Contains(u.version, "commit_id") {
|
||||
ref = "%{commit_id}"
|
||||
} else if u.isRelease && u.tag == "v"+u.version {
|
||||
ref = "v%{version}"
|
||||
} else if u.isRelease && u.tag == u.version {
|
||||
ref = "%{version}"
|
||||
} else if ref == "" {
|
||||
ref = u.commitIsh
|
||||
}
|
||||
if ref == "" {
|
||||
return "", fmt.Errorf("source reference is empty")
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// sourceURLForSpec returns the Source0 URL for the spec file. It prefers
|
||||
// hoster tarballs for repo-root modules, and uses proxy.golang.org zips for
|
||||
// repo subdirectories or unsupported hosters. It returns an error for
|
||||
// commit-pinned subdirectory modules because the proxy requires a canonical
|
||||
// pseudo-version.
|
||||
func (u *upstream) sourceURLForSpec(modulePath string) (string, error) {
|
||||
ref, err := u.sourceRefForSpec()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if moduleUsesRepoSubdir(modulePath, u.repoURL) {
|
||||
if strings.Contains(ref, "commit_id") {
|
||||
return "", fmt.Errorf("module proxy Source0 for commit-pinned subdirectory module %q requires a canonical pseudo-version", modulePath)
|
||||
}
|
||||
return fmt.Sprintf("https://proxy.golang.org/%s/@v/%s.zip#/%%{_name}-%%{version}.zip",
|
||||
moduleProxyEscapedPath(modulePath), moduleProxyVersionForSpec(ref)), nil
|
||||
}
|
||||
tarURL, err := u.tarballURLForRef(ref, "gz")
|
||||
if err == nil {
|
||||
return fmt.Sprintf("%s#/%%{_name}-%%{version}.tar.gz", tarURL), nil
|
||||
}
|
||||
if err != errUnsupportedHoster || modulePath == "" || strings.Contains(ref, "commit_id") {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("https://proxy.golang.org/%s/@v/%s.zip#/%%{_name}-%%{version}.zip",
|
||||
moduleProxyEscapedPath(modulePath), moduleProxyVersionForSpec(ref)), nil
|
||||
}
|
||||
|
||||
func (u *upstream) tarballFromHoster() error {
|
||||
tarURL, err := u.tarballUrl()
|
||||
if err != nil {
|
||||
@@ -272,24 +449,29 @@ type goListPackage struct {
|
||||
Imports []string
|
||||
TestImports []string
|
||||
XTestImports []string
|
||||
Error *goListPackageError
|
||||
Error *struct {
|
||||
Err string
|
||||
}
|
||||
}
|
||||
|
||||
type goListPackageError struct {
|
||||
Err string
|
||||
func shouldIgnoreGoDependency(p, repo string) bool {
|
||||
if p == "" {
|
||||
return true
|
||||
}
|
||||
// Strip packages that are included in the repository we are packaging.
|
||||
if strings.HasPrefix(p, repo+"/") || p == repo {
|
||||
return true
|
||||
}
|
||||
if p == "C" {
|
||||
// TODO: maybe parse the comments to figure out C deps from pkg-config files?
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func addGoDependencies(deps map[string]bool, repo string, imports []string) {
|
||||
for _, p := range imports {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
// Strip packages that are included in the repository we are packaging.
|
||||
if strings.HasPrefix(p, repo+"/") || p == repo {
|
||||
continue
|
||||
}
|
||||
if p == "C" {
|
||||
// TODO: maybe parse the comments to figure out C deps from pkg-config files?
|
||||
if shouldIgnoreGoDependency(p, repo) {
|
||||
continue
|
||||
}
|
||||
deps[p] = true
|
||||
@@ -388,44 +570,30 @@ func (u *upstream) findDependencies(gopath, repo string) error {
|
||||
delete(testDeps, line)
|
||||
}
|
||||
|
||||
// Resolve all packages to the root of their repository.
|
||||
//roots := make(map[string]bool)
|
||||
//for dep := range godependencies {
|
||||
// rr, err := vcs.RepoRootForImportPath(dep, false)
|
||||
// if err != nil {
|
||||
// log.Printf("Could not determine repo path for import path %q: %v\n", dep, err)
|
||||
// continue
|
||||
// }
|
||||
// roots[rr.Root] = true
|
||||
//}
|
||||
|
||||
//u.repoDeps = make([]string, 0, len(godependencies))
|
||||
//for root := range roots {
|
||||
// u.repoDeps = append(u.repoDeps, root)
|
||||
//}
|
||||
|
||||
u.setRepoDependencies(runtimeDeps, testDeps)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeUpstreamSourceTarball downloads the specified Go package from the Internet,
|
||||
// makeUpstreamSourceTarball downloads the source module from the Internet,
|
||||
// checks out the specified revision, determines the version number, removes
|
||||
// vendored dependencies, and creates a tarball of the upstream source code.
|
||||
// It also discovers main packages and dependencies, and may check out a release
|
||||
// tag different from the requested revision when pkgVersionFromGit selects one.
|
||||
// It returns an upstream struct describing the downloaded package.
|
||||
func makeUpstreamSourceTarball(repo, revision string, forcePrerelease bool) (*upstream, error) {
|
||||
func makeUpstreamSourceTarball(requestedPath, sourceRepo, revision string, forcePrerelease bool) (*upstream, error) {
|
||||
gopath, err := os.MkdirTemp("", "pack-tmp")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create tmp dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(gopath)
|
||||
repoDir := filepath.Join(gopath, "src", repo)
|
||||
repoDir := filepath.Join(gopath, "src", sourceRepo)
|
||||
|
||||
var u upstream
|
||||
|
||||
log.Printf("Downloading %q\n", repo+"/...")
|
||||
if err := u.get(gopath, repo, revision); err != nil {
|
||||
return nil, fmt.Errorf("go get: %w", err)
|
||||
log.Printf("Downloading %q\n", sourceRepo+"/...")
|
||||
if err := u.get(gopath, sourceRepo, requestedPath, revision); err != nil {
|
||||
return nil, fmt.Errorf("get source: %w", err)
|
||||
}
|
||||
|
||||
// Verify early this repository uses git (we call pkgVersionFromGit later):
|
||||
@@ -453,22 +621,43 @@ func makeUpstreamSourceTarball(repo, revision string, forcePrerelease bool) (*up
|
||||
|
||||
log.Printf("Determining upstream version number\n")
|
||||
|
||||
u.version, err = pkgVersionFromGit(repoDir, &u, revision, forcePrerelease)
|
||||
preferredRevision := revision
|
||||
if preferredRevision == "" {
|
||||
// Reuses the cache populated by u.get above when available.
|
||||
if info, err := getPkgsiteInfo(context.TODO(), requestedPath); err == nil {
|
||||
preferredRevision = info.Package.Version
|
||||
log.Printf("Using pkg.go.dev latest version %q as the preferred release", preferredRevision)
|
||||
}
|
||||
}
|
||||
u.version, err = pkgVersionFromGit(repoDir, &u, preferredRevision, forcePrerelease)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get package version from Git: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Package version is %q\n", u.version)
|
||||
|
||||
if err := u.findMains(gopath, repo); err != nil {
|
||||
if u.isRelease && u.tag != "" {
|
||||
// pkgVersionFromGit may select a release tag different from the revision
|
||||
// passed by the user; dependency and tarball discovery must use that tree.
|
||||
cmd := exec.Command("git", "-c", "advice.detachedHead=false", "checkout", u.tag)
|
||||
cmd.Dir = repoDir
|
||||
cmd.Env = passthroughEnv()
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Println("makeUpstreamSourceTarball: Running", cmd, "in", cmd.Dir)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("git checkout release tag %q: %w", u.tag, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := u.findMains(gopath, sourceRepo); err != nil {
|
||||
return nil, fmt.Errorf("find mains: %w", err)
|
||||
}
|
||||
|
||||
if err := u.findDependencies(gopath, repo); err != nil {
|
||||
if err := u.findDependencies(gopath, sourceRepo); err != nil {
|
||||
return nil, fmt.Errorf("find dependencies: %w", err)
|
||||
}
|
||||
|
||||
if err := u.tar(gopath, repo); err != nil {
|
||||
if err := u.tar(gopath, sourceRepo); err != nil {
|
||||
return nil, fmt.Errorf("tar: %w", err)
|
||||
}
|
||||
|
||||
@@ -538,11 +727,11 @@ func shortHostName(gopkg string, allowUnknownHoster bool) (host string, err erro
|
||||
"gitlab.com": "gitlab",
|
||||
"go.bug.st": "bugst",
|
||||
"go.cypherpunks.ru": "cypherpunks",
|
||||
"go.yaml.in": "yaml",
|
||||
"go.mongodb.org": "mongodb",
|
||||
"go.opentelemetry.io": "opentelemetry",
|
||||
"go.step.sm": "step",
|
||||
"go.uber.org": "uber",
|
||||
"go.yaml.in": "yaml",
|
||||
"go4.org": "go4",
|
||||
"gocloud.dev": "gocloud",
|
||||
"golang.org": "golang",
|
||||
@@ -697,14 +886,16 @@ func mainPack(args []string, usage func()) {
|
||||
// Remove URL scheme if present (https://, http://, git://, etc.)
|
||||
gopkg = strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(gopkg, "https://"), "http://"), "git://")
|
||||
|
||||
// Verify that the provided argument is a valid Go package import path
|
||||
rr, err := vcs.RepoRootForImportPath(gopkg, false)
|
||||
// Verify the provided argument using the official pkg.go.dev API. Keep the
|
||||
// requested path for naming and go_import_path; use the module path only for
|
||||
// source checkout and tarball generation.
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
log.Fatalf("Verifying arguments: %v — did you specify a Go package import path?", err)
|
||||
}
|
||||
if gopkg != rr.Root {
|
||||
log.Printf("Continuing with repository root %q instead of specified import path %q", rr.Root, gopkg)
|
||||
gopkg = rr.Root
|
||||
sourceGopkg := sourceImportPathForPackage(gopkg, info)
|
||||
if sourceGopkg != gopkg {
|
||||
log.Printf("Using module root %q as source checkout for specified import path %q", sourceGopkg, gopkg)
|
||||
}
|
||||
|
||||
// Set default source and binary package names.
|
||||
@@ -750,25 +941,33 @@ func mainPack(args []string, usage func()) {
|
||||
// NOTE: directory existence is checked after determining final openRuyiSrc
|
||||
|
||||
// Create a tarball of the upstream source
|
||||
u, err := makeUpstreamSourceTarball(gopkg, gitRevision, forcePrerelease)
|
||||
u, err := makeUpstreamSourceTarball(gopkg, sourceGopkg, gitRevision, forcePrerelease)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not create a tarball of the upstream source: %v\n", err)
|
||||
}
|
||||
|
||||
if pkgType == typeGuess {
|
||||
if u.firstMain != "" {
|
||||
log.Printf("Assuming you are packaging a program (because %q defines a main package), use -type to override\n", u.firstMain)
|
||||
switch guessedPackageType(u) {
|
||||
case typeProgram:
|
||||
if u.packageName == "main" {
|
||||
log.Printf("Assuming you are packaging a program (because pkg.go.dev reports package %q as main), use -type to override\n", gopkg)
|
||||
} else {
|
||||
log.Printf("Assuming you are packaging a program (because %q defines a main package), use -type to override\n", u.firstMain)
|
||||
}
|
||||
pkgType = typeProgram
|
||||
openRuyiSrc = nameFromGopkg(gopkg, pkgType, customProgPkgName, allowUnknownHoster)
|
||||
} else {
|
||||
default:
|
||||
if u.firstMain != "" {
|
||||
log.Printf("Found main package %q, but pkg.go.dev reports root package %q; assuming library, use -type to override\n", u.firstMain, u.packageName)
|
||||
}
|
||||
pkgType = typeLibrary
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know the final package name, check output directory
|
||||
info, err := os.Stat(openRuyiSrc)
|
||||
dirInfo, err := os.Stat(openRuyiSrc)
|
||||
if err == nil {
|
||||
if !info.IsDir() {
|
||||
if !dirInfo.IsDir() {
|
||||
log.Fatalf("%q exists but is not a directory\n", openRuyiSrc)
|
||||
}
|
||||
entries, err := os.ReadDir(openRuyiSrc)
|
||||
|
||||
+352
@@ -58,6 +58,278 @@ func TestPkgsiteLicenseExpression(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPkgsiteLicenseFilePaths(t *testing.T) {
|
||||
licenses := []pkgsiteLicense{
|
||||
{FilePath: "License", Types: []string{"MIT"}},
|
||||
{FilePath: "internal/LICENSE", Types: []string{"Apache-2.0"}},
|
||||
{FilePath: "License", Types: []string{"MIT"}},
|
||||
}
|
||||
got := strings.Join(pkgsiteLicenseFilePaths(licenses), ",")
|
||||
if want := "License"; got != want {
|
||||
t.Fatalf("pkgsiteLicenseFilePaths() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanSpecAssetPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{in: "Readme", want: "Readme"},
|
||||
{in: "/docs/readme.md", want: "docs/readme.md"},
|
||||
{in: "../README.md", want: ""},
|
||||
{in: "docs/../README.md", want: ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := cleanSpecAssetPath(tt.in); got != tt.want {
|
||||
t.Fatalf("cleanSpecAssetPath(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSummaryFromReadme(t *testing.T) {
|
||||
readme := `
|
||||
# Project
|
||||
|
||||
[](https://example.invalid)
|
||||
<p align="center">
|
||||
|
||||
This is the first useful line.
|
||||
`
|
||||
if got, want := summaryFromReadme(readme), "This is the first useful line"; got != want {
|
||||
t.Fatalf("summaryFromReadme() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnusableSummary(t *testing.T) {
|
||||
info := &pkgsiteInfo{
|
||||
Package: pkgsitePackage{Name: "runewidth"},
|
||||
Module: pkgsiteModule{Path: "github.com/mattn/go-runewidth"},
|
||||
}
|
||||
if !unusableSummary("go-runewidth", "github.com/mattn/go-runewidth", info) {
|
||||
t.Fatalf("expected basename summary to be unusable")
|
||||
}
|
||||
if unusableSummary("Determines terminal display width", "github.com/mattn/go-runewidth", info) {
|
||||
t.Fatalf("expected descriptive summary to be usable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanSummaryCandidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{name: "strips html", in: `<p align="center">Package termenv styles terminals.</p>`, want: "Styles terminals"},
|
||||
{name: "strips godoc package prefix", in: "Package isatty implements interface to isatty.", want: "Implements interface to isatty"},
|
||||
{name: "strips markdown image", in: ` Package css parses CSS.`, want: "Parses CSS"},
|
||||
{name: "strips markdown link and code", in: "A [`Writer`](https://example.invalid) for ANSI output.", want: "A Writer for ANSI output"},
|
||||
{name: "uses first sentence", in: "Package termenv styles terminals. It supports colors.", want: "Styles terminals"},
|
||||
{name: "rejects html-only", in: `<p align="center">`, want: ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := cleanSummaryCandidate(tt.in); got != tt.want {
|
||||
t.Fatalf("cleanSummaryCandidate() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarballURLForRef(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
repoURL string
|
||||
ref string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "github",
|
||||
repoURL: "https://github.com/google/go-cmp",
|
||||
ref: "v0.7.0",
|
||||
want: "https://github.com/google/go-cmp/archive/v0.7.0.tar.gz",
|
||||
},
|
||||
{
|
||||
name: "gitlab subgroup",
|
||||
repoURL: "https://gitlab.com/group/subgroup/project",
|
||||
ref: "v1.2.3",
|
||||
want: "https://gitlab.com/group/subgroup/project/-/archive/v1.2.3/project-v1.2.3.tar.gz",
|
||||
},
|
||||
{
|
||||
name: "sourcehut",
|
||||
repoURL: "https://git.sr.ht/~user/project",
|
||||
ref: "v1.0.0",
|
||||
want: "https://git.sr.ht/~user/project/archive/v1.0.0.tar.gz",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
u := upstream{repoURL: tt.repoURL}
|
||||
got, err := u.tarballURLForRef(tt.ref, "gz")
|
||||
if err != nil {
|
||||
t.Fatalf("tarballURLForRef() returned error: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("tarballURLForRef() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitCloneURLFromRepoURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "keeps github clone URL",
|
||||
in: "https://github.com/google/go-cmp.git",
|
||||
want: "https://github.com/google/go-cmp",
|
||||
},
|
||||
{
|
||||
name: "converts Go source browser URL",
|
||||
in: "https://cs.opensource.google/go/x/net",
|
||||
want: "https://go.googlesource.com/net",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := gitCloneURLFromRepoURL(tt.in); got != tt.want {
|
||||
t.Fatalf("gitCloneURLFromRepoURL(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuessedPackageTypeUsesRootPackageName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
u upstream
|
||||
want packageType
|
||||
}{
|
||||
{
|
||||
name: "library root with command subpackage stays library",
|
||||
u: upstream{packageName: "yaml", firstMain: "go.yaml.in/yaml/v4/cmd/go-yaml"},
|
||||
want: typeLibrary,
|
||||
},
|
||||
{
|
||||
name: "library root without command subpackage stays library",
|
||||
u: upstream{packageName: "yaml"},
|
||||
want: typeLibrary,
|
||||
},
|
||||
{
|
||||
name: "root main is program",
|
||||
u: upstream{packageName: "main", firstMain: "example.com/tool"},
|
||||
want: typeProgram,
|
||||
},
|
||||
{
|
||||
name: "fallback to old main detection when pkgsite name missing",
|
||||
u: upstream{firstMain: "example.com/tool"},
|
||||
want: typeProgram,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := guessedPackageType(&tt.u); got != tt.want {
|
||||
t.Fatalf("guessedPackageType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceImportPathForPackageUsesModuleRoot(t *testing.T) {
|
||||
info := &pkgsiteInfo{Package: pkgsitePackage{ModulePath: "github.com/example/project"}}
|
||||
if got, want := sourceImportPathForPackage("github.com/example/project/cmd/tool", info), "github.com/example/project"; got != want {
|
||||
t.Fatalf("sourceImportPathForPackage() = %q, want %q", got, want)
|
||||
}
|
||||
info = &pkgsiteInfo{Module: pkgsiteModule{Path: "github.com/example/module"}}
|
||||
if got, want := sourceImportPathForPackage("github.com/example/module/pkg", info), "github.com/example/module"; got != want {
|
||||
t.Fatalf("sourceImportPathForPackage(module fallback) = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := sourceImportPathForPackage("example.com/no-module", nil), "example.com/no-module"; got != want {
|
||||
t.Fatalf("sourceImportPathForPackage(nil) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRepoDependenciesSeparatesRuntimeAndTestOnly(t *testing.T) {
|
||||
u := upstream{}
|
||||
u.setRepoDependencies(
|
||||
map[string]bool{
|
||||
"github.com/runtime/dep": true,
|
||||
"github.com/shared/dep": true,
|
||||
},
|
||||
map[string]bool{
|
||||
"github.com/shared/dep": true,
|
||||
"github.com/testonly/dep": true,
|
||||
"github.com/testonly/dep2": true,
|
||||
},
|
||||
)
|
||||
|
||||
if got, want := strings.Join(u.repoRunDeps, ","), "github.com/runtime/dep,github.com/shared/dep"; got != want {
|
||||
t.Fatalf("repoRunDeps = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := strings.Join(u.repoTestDeps, ","), "github.com/testonly/dep,github.com/testonly/dep2"; got != want {
|
||||
t.Fatalf("repoTestDeps = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := strings.Join(u.repoDeps, ","), "github.com/runtime/dep,github.com/shared/dep,github.com/testonly/dep,github.com/testonly/dep2"; got != want {
|
||||
t.Fatalf("repoDeps = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleProxyEscapedPath(t *testing.T) {
|
||||
if got, want := moduleProxyEscapedPath("github.com/Azure/azure-sdk-for-go"), "github.com/!azure/azure-sdk-for-go"; got != want {
|
||||
t.Fatalf("moduleProxyEscapedPath() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleProxyVersionForSpec(t *testing.T) {
|
||||
if got, want := moduleProxyVersionForSpec("v1.0.0-RC1"), "v1.0.0-!r!c1"; got != want {
|
||||
t.Fatalf("moduleProxyVersionForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := moduleProxyVersionForSpec("v%{version}"), "v%{version}"; got != want {
|
||||
t.Fatalf("moduleProxyVersionForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleUsesRepoSubdir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
modulePath string
|
||||
repoURL string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "real submodule",
|
||||
modulePath: "github.com/charmbracelet/x/ansi",
|
||||
repoURL: "https://github.com/charmbracelet/x",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "semantic import version suffix is not a repo subdir",
|
||||
modulePath: "github.com/aymanbagabas/go-osc52/v2",
|
||||
repoURL: "https://github.com/aymanbagabas/go-osc52",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "vanity module does not match repo host path",
|
||||
modulePath: "go.yaml.in/yaml/v4",
|
||||
repoURL: "https://github.com/yaml/go-yaml",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := moduleUsesRepoSubdir(tt.modulePath, tt.repoURL); got != tt.want {
|
||||
t.Fatalf("moduleUsesRepoSubdir() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPkgsitePackageRetriesAmbiguousPathWithLongestModule(t *testing.T) {
|
||||
oldClient := pkgsiteHTTPClient
|
||||
defer func() { pkgsiteHTTPClient = oldClient }()
|
||||
@@ -105,3 +377,83 @@ func TestGetPkgsitePackageRetriesAmbiguousPathWithLongestModule(t *testing.T) {
|
||||
t.Fatalf("requests = %#v, want retry with longest module path", requested)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceURLForSpecUsesCommitIDMacro(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/example/project",
|
||||
version: "0+git20260522.abcdef\n%define commit_id 0123456789abcdef",
|
||||
}
|
||||
got, err := u.sourceURLForSpec("github.com/example/project")
|
||||
if err != nil {
|
||||
t.Fatalf("sourceURLForSpec() returned error: %v", err)
|
||||
}
|
||||
want := "https://github.com/example/project/archive/%{commit_id}.tar.gz#/%{_name}-%{version}.tar.gz"
|
||||
if got != want {
|
||||
t.Fatalf("sourceURLForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceURLForSpecKeepsVersionMacroForMatchingReleaseTag(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/example/project",
|
||||
version: "1.2.3",
|
||||
tag: "v1.2.3",
|
||||
isRelease: true,
|
||||
}
|
||||
got, err := u.sourceURLForSpec("github.com/example/project")
|
||||
if err != nil {
|
||||
t.Fatalf("sourceURLForSpec() returned error: %v", err)
|
||||
}
|
||||
want := "https://github.com/example/project/archive/v%{version}.tar.gz#/%{_name}-%{version}.tar.gz"
|
||||
if got != want {
|
||||
t.Fatalf("sourceURLForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceURLForSpecFallsBackToModuleProxy(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://go.googlesource.com/net",
|
||||
version: "0.55.0",
|
||||
tag: "v0.55.0",
|
||||
isRelease: true,
|
||||
}
|
||||
got, err := u.sourceURLForSpec("golang.org/x/net")
|
||||
if err != nil {
|
||||
t.Fatalf("sourceURLForSpec() returned error: %v", err)
|
||||
}
|
||||
want := "https://proxy.golang.org/golang.org/x/net/@v/v%{version}.zip#/%{_name}-%{version}.zip"
|
||||
if got != want {
|
||||
t.Fatalf("sourceURLForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceURLForSpecUsesModuleProxyForRepoSubmodule(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/charmbracelet/x",
|
||||
version: "0.1.0",
|
||||
tag: "v0.1.0",
|
||||
isRelease: true,
|
||||
}
|
||||
got, err := u.sourceURLForSpec("github.com/charmbracelet/x/ansi")
|
||||
if err != nil {
|
||||
t.Fatalf("sourceURLForSpec() returned error: %v", err)
|
||||
}
|
||||
want := "https://proxy.golang.org/github.com/charmbracelet/x/ansi/@v/v%{version}.zip#/%{_name}-%{version}.zip"
|
||||
if got != want {
|
||||
t.Fatalf("sourceURLForSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceURLForSpecRejectsCommitIDForRepoSubmodule(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/charmbracelet/x",
|
||||
version: "0+git20260522.abcdef\n%define commit_id abcdef1234567890",
|
||||
}
|
||||
_, err := u.sourceURLForSpec("github.com/charmbracelet/x/ansi")
|
||||
if err == nil {
|
||||
t.Fatalf("sourceURLForSpec() succeeded for commit-pinned submodule, want error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "canonical pseudo-version") {
|
||||
t.Fatalf("sourceURLForSpec() error = %v, want canonical pseudo-version message", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -10,6 +11,65 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type specAssetFiles struct {
|
||||
licenseFiles []string
|
||||
readmeFile string
|
||||
}
|
||||
|
||||
func cleanSpecAssetPath(path string) string {
|
||||
path = filepath.ToSlash(strings.TrimSpace(path))
|
||||
path = strings.TrimLeft(path, "/")
|
||||
if path == "" || strings.HasPrefix(path, "../") || strings.Contains(path, "/../") || path == ".." {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func pkgsiteLicenseFilePaths(licenses []pkgsiteLicense) []string {
|
||||
topLevel := make([]pkgsiteLicense, 0, len(licenses))
|
||||
for _, license := range licenses {
|
||||
if !strings.Contains(strings.Trim(license.FilePath, "/"), "/") {
|
||||
topLevel = append(topLevel, license)
|
||||
}
|
||||
}
|
||||
if len(topLevel) > 0 {
|
||||
licenses = topLevel
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
paths := make([]string, 0, len(licenses))
|
||||
for _, license := range licenses {
|
||||
path := cleanSpecAssetPath(license.FilePath)
|
||||
if path == "" || seen[path] {
|
||||
continue
|
||||
}
|
||||
seen[path] = true
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths)
|
||||
return paths
|
||||
}
|
||||
|
||||
func getSpecAssetFilesForGopkg(gopkg string) (specAssetFiles, error) {
|
||||
info, err := getPkgsiteInfo(context.TODO(), gopkg)
|
||||
if err != nil {
|
||||
return specAssetFiles{}, err
|
||||
}
|
||||
|
||||
licenses := info.Module.Licenses
|
||||
if len(licenses) == 0 {
|
||||
licenses = info.Package.Licenses
|
||||
}
|
||||
|
||||
assets := specAssetFiles{
|
||||
licenseFiles: pkgsiteLicenseFilePaths(licenses),
|
||||
}
|
||||
if info.Module.Readme != nil {
|
||||
assets.readmeFile = cleanSpecAssetPath(info.Module.Readme.Filepath)
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version string,
|
||||
pkgType packageType, u *upstream) error {
|
||||
|
||||
@@ -35,14 +95,18 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
log.Printf("Could not determine license for %q: %v\n", gopkg, err)
|
||||
license = "TODO"
|
||||
}
|
||||
assetFiles, err := getSpecAssetFilesForGopkg(gopkg)
|
||||
if err != nil {
|
||||
log.Printf("Could not determine license/doc file paths for %q: %v\n", gopkg, err)
|
||||
}
|
||||
upstreamName := filepath.Base(gopkg)
|
||||
if regexp.MustCompile(`^v\d+$`).MatchString(upstreamName) {
|
||||
upstreamName = filepath.Base(filepath.Dir(gopkg))
|
||||
}
|
||||
owner, repo, err := findGitHubRepo(gopkg)
|
||||
repoURL, err := getRepoURLForGopkg(gopkg)
|
||||
if err != nil {
|
||||
owner = "TODO"
|
||||
repo = "TODO"
|
||||
log.Printf("Could not determine repository URL for %q: %v\n", gopkg, err)
|
||||
repoURL = "TODO"
|
||||
}
|
||||
|
||||
// Write the spec file content
|
||||
@@ -72,8 +136,8 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
// Header
|
||||
fmt.Fprintf(f, "Name: %s\n", openRuyiSrc)
|
||||
|
||||
// Some times typeLibrary is treat as typeProgram,
|
||||
// So we add an additional Name line, and keep one of those mannually
|
||||
// Program packages may need manual naming cleanup; keep the library-form
|
||||
// name visible as a second Name line until that workflow is redesigned.
|
||||
switch pkgType {
|
||||
case typeProgram:
|
||||
fmt.Fprintf(f, "Name: %s\n", openRuyiLib)
|
||||
@@ -83,15 +147,18 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
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, "URL: %s\n", repoURL)
|
||||
fmt.Fprintf(f, "#!RemoteAsset\n")
|
||||
// If the computed version text contains a commit_id definition (see pkgVersionFromGit),
|
||||
// use the commit_id tarball instead of v%{version}.tar.gz
|
||||
if strings.Contains(u.version, "commit_id") {
|
||||
fmt.Fprintf(f, "Source0: https://github.com/%s/%s/archive/%%{commit_id}.tar.gz#/%%{_name}-%%{version}.tar.gz\n", owner, repo)
|
||||
} else {
|
||||
fmt.Fprintf(f, "Source0: https://github.com/%s/%s/archive/v%%{version}.tar.gz#/%%{_name}-%%{version}.tar.gz\n", owner, repo)
|
||||
sourceModulePath := u.modulePath
|
||||
if sourceModulePath == "" {
|
||||
sourceModulePath = gopkg
|
||||
}
|
||||
sourceURL, err := u.sourceURLForSpec(sourceModulePath)
|
||||
if err != nil {
|
||||
log.Printf("Could not determine remote source URL for %q: %v\n", gopkg, err)
|
||||
sourceURL = "TODO"
|
||||
}
|
||||
fmt.Fprintf(f, "Source0: %s\n", sourceURL)
|
||||
|
||||
switch pkgType {
|
||||
case typeLibrary:
|
||||
@@ -137,7 +204,7 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
fmt.Fprintf(f, "\n")
|
||||
|
||||
// %files
|
||||
writeRPMFilesSection(f, openRuyiSrc, openRuyiLib, openRuyiProgram, pkgType)
|
||||
writeRPMFilesSection(f, openRuyiSrc, openRuyiLib, openRuyiProgram, pkgType, assetFiles)
|
||||
|
||||
// %changelog
|
||||
fmt.Fprintf(f, "%%changelog\n")
|
||||
@@ -192,45 +259,50 @@ func writeRPMProgramSubpackage(f *os.File, gopkg, openRuyiProgram, openRuyiSrc,
|
||||
fmt.Fprintf(f, "This package contains the %s executable.\n", filepath.Base(gopkg))
|
||||
}
|
||||
|
||||
func writeRPMFilesSection(f *os.File, openRuyiSrc, openRuyiLib, openRuyiProgram string, pkgType packageType) {
|
||||
func writeLicenseAndDocFiles(f *os.File, assetFiles specAssetFiles, includeDoc bool) {
|
||||
for _, licenseFile := range assetFiles.licenseFiles {
|
||||
fmt.Fprintf(f, "%%license %s\n", licenseFile)
|
||||
}
|
||||
if includeDoc && assetFiles.readmeFile != "" {
|
||||
fmt.Fprintf(f, "%%doc %s\n", assetFiles.readmeFile)
|
||||
}
|
||||
}
|
||||
|
||||
func writeRPMFilesSection(f *os.File, openRuyiSrc, openRuyiLib, openRuyiProgram string, pkgType packageType, assetFiles specAssetFiles) {
|
||||
switch pkgType {
|
||||
case typeLibrary:
|
||||
fmt.Fprintf(f, "%%files\n")
|
||||
fmt.Fprintf(f, "%%license LICENSE*\n")
|
||||
fmt.Fprintf(f, "%%doc README*\n")
|
||||
writeLicenseAndDocFiles(f, assetFiles, true)
|
||||
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")
|
||||
writeLicenseAndDocFiles(f, assetFiles, true)
|
||||
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")
|
||||
writeLicenseAndDocFiles(f, assetFiles, true)
|
||||
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")
|
||||
writeLicenseAndDocFiles(f, assetFiles, false)
|
||||
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")
|
||||
writeLicenseAndDocFiles(f, assetFiles, true)
|
||||
fmt.Fprintf(f, "%%{_bindir}/%%{_name}\n")
|
||||
fmt.Fprintf(f, "\n")
|
||||
// 库子包文件
|
||||
fmt.Fprintf(f, "%%files -n %s\n", openRuyiLib)
|
||||
fmt.Fprintf(f, "%%license LICENSE*\n")
|
||||
writeLicenseAndDocFiles(f, assetFiles, false)
|
||||
fmt.Fprintf(f, "%%{go_sys_gopath}/%%{go_import_path}\n")
|
||||
fmt.Fprintf(f, "\n")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user