forked from misaka00251/go2spec
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fb570e532 | ||
|
|
386eb655e8 |
@@ -1,28 +0,0 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2026, Institute of Software, Chinese Academy of Sciences (ISCAS)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,75 +0,0 @@
|
||||
# go2spec
|
||||
|
||||
[](#overview)
|
||||
[](LICENSE)
|
||||
[](README.md)
|
||||
[](README.zh.md)
|
||||
|
||||
## Overview
|
||||
|
||||
go2spec is a Go package helper for the openRuyi RPM packaging workflow. It collects upstream metadata from the Go ecosystem, generates source archives, and writes RPM spec files.
|
||||
|
||||
## Installation
|
||||
|
||||
### Build From Source
|
||||
|
||||
```bash
|
||||
git clone <go2spec-repo-url>
|
||||
cd go2spec
|
||||
go build -o go2spec .
|
||||
```
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
./go2spec help
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Generate a spec
|
||||
|
||||
```bash
|
||||
go2spec pack golang.org/x/net
|
||||
```
|
||||
|
||||
The command creates a file layout similar to this in the current directory:
|
||||
|
||||
```text
|
||||
go-golang-x-net/
|
||||
go-golang-x-net/go-golang-x-net.spec
|
||||
go-golang-x-net_<version>.orig.tar.<gz|xz>
|
||||
```
|
||||
|
||||
### Check existing spec updates without writing files
|
||||
|
||||
```bash
|
||||
go2spec update -n /path/to/SPECS
|
||||
```
|
||||
|
||||
`update` scans `/path/to/SPECS/go-*/*.spec`, then checks the latest upstream version.
|
||||
|
||||
### Run updates concurrently
|
||||
|
||||
```bash
|
||||
go2spec update -j 4 /path/to/SPECS
|
||||
```
|
||||
|
||||
> Always run dry run mode first before modifying real spec files.
|
||||
|
||||
## Documentation
|
||||
|
||||
| Area | English | Chinese |
|
||||
|---|---|---|
|
||||
| Main README | [README.md](README.md) | [README.zh.md](docs/zh/README.zh.md) |
|
||||
| `pack` command | [docs/en/pack.en.md](docs/en/pack.en.md) | [docs/zh/pack.zh.md](docs/zh/pack.zh.md) |
|
||||
| `update` command | [docs/en/update.en.md](docs/en/update.en.md) | [docs/zh/update.zh.md](docs/zh/update.zh.md) |
|
||||
|
||||
## Contributing
|
||||
|
||||
- Read the [openRuyi Code of Conduct](https://openruyi.cn/governance/legal/code-of-conduct) and agree to follow it.
|
||||
- Read the [AI-assisted Contribution Policy](https://openruyi.cn/governance/policy/ai-contribution-policy), and note that this Pull Request contains substantial AI-assisted content when applicable.
|
||||
|
||||
## License
|
||||
|
||||
go2spec uses the BSD-3-Clause license. See [LICENSE](LICENSE).
|
||||
@@ -1,75 +0,0 @@
|
||||
# go2spec
|
||||
|
||||
[](#项目概览)
|
||||
[](LICENSE)
|
||||
[](README.md)
|
||||
[](README.zh.md)
|
||||
|
||||
## 项目概览
|
||||
|
||||
go2spec 是面向 openRuyi RPM 打包流程的 Go 包辅助工具。它从 Go 生态获取上游元数据,生成源码归档,并输出 RPM spec。
|
||||
|
||||
## 安装指南
|
||||
|
||||
### 从源码构建
|
||||
|
||||
```bash
|
||||
git clone <go2spec-repo-url>
|
||||
cd go2spec
|
||||
go build -o go2spec .
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
./go2spec help
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 生成 spec
|
||||
|
||||
```bash
|
||||
go2spec pack golang.org/x/net
|
||||
```
|
||||
|
||||
该命令会在当前目录生成类似下面的文件结构:
|
||||
|
||||
```text
|
||||
go-golang-x-net/
|
||||
go-golang-x-net/go-golang-x-net.spec
|
||||
go-golang-x-net_<version>.orig.tar.<gz|xz>
|
||||
```
|
||||
|
||||
### 不写入文件地检查现有 spec 更新
|
||||
|
||||
```bash
|
||||
go2spec update -n /path/to/SPECS
|
||||
```
|
||||
|
||||
`update` 会扫描 `/path/to/SPECS/go-*/*.spec`,然后检查上游最新版本。
|
||||
|
||||
### 并发执行更新
|
||||
|
||||
```bash
|
||||
go2spec update -j 4 /path/to/SPECS
|
||||
```
|
||||
|
||||
> 建议始终先执行 dry run,再修改真实 spec 文件。
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 内容 | English | 中文 |
|
||||
|---|---|---|
|
||||
| 主 README | [README.md](README.md) | [README.zh.md](README.zh.md) |
|
||||
| `pack` 命令 | [docs/en/pack.en.md](docs/en/pack.en.md) | [docs/zh/pack.zh.md](docs/zh/pack.zh.md) |
|
||||
| `update` 命令 | [docs/en/update.en.md](docs/en/update.en.md) | [docs/zh/update.zh.md](docs/zh/update.zh.md) |
|
||||
|
||||
## 贡献
|
||||
|
||||
- 阅读 [openRuyi 行为准则](https://openruyi.cn/governance/legal/code-of-conduct) 并同意遵守。
|
||||
- 阅读 [AI 辅助贡献政策](https://openruyi.cn/governance/policy/ai-contribution-policy),且此 Pull Request 包含实质性的 AI 辅助内容时需要说明。
|
||||
|
||||
## 许可证
|
||||
|
||||
go2spec 使用 BSD-3-Clause 许可证,见 [LICENSE](LICENSE)。
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const specAuthorEnv = "GO2SPEC_AUTHOR"
|
||||
|
||||
type specAuthor struct {
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
func (a specAuthor) IsZero() bool {
|
||||
return a.Name == "" && a.Email == ""
|
||||
}
|
||||
|
||||
func (a specAuthor) String() string {
|
||||
return fmt.Sprintf("%s <%s>", a.Name, a.Email)
|
||||
}
|
||||
|
||||
func parseSpecAuthor(raw string) (specAuthor, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return specAuthor{}, fmt.Errorf("author is empty")
|
||||
}
|
||||
if strings.ContainsAny(raw, "\r\n") {
|
||||
return specAuthor{}, fmt.Errorf("author must be a single line")
|
||||
}
|
||||
if !strings.HasSuffix(raw, ">") {
|
||||
return specAuthor{}, fmt.Errorf("author must use Name <email> format")
|
||||
}
|
||||
|
||||
start := strings.LastIndex(raw, "<")
|
||||
if start < 0 {
|
||||
return specAuthor{}, fmt.Errorf("author must use Name <email> format")
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(raw[:start])
|
||||
email := strings.TrimSpace(strings.TrimSuffix(raw[start+1:], ">"))
|
||||
if name == "" || email == "" {
|
||||
return specAuthor{}, fmt.Errorf("author must include both name and email")
|
||||
}
|
||||
if strings.ContainsAny(email, "<>") {
|
||||
return specAuthor{}, fmt.Errorf("author email must not contain angle brackets")
|
||||
}
|
||||
|
||||
return specAuthor{Name: name, Email: email}, nil
|
||||
}
|
||||
|
||||
func resolveSpecAuthor(raw string) (specAuthor, error) {
|
||||
if strings.TrimSpace(raw) != "" {
|
||||
return parseSpecAuthor(raw)
|
||||
}
|
||||
|
||||
if envAuthor, ok := os.LookupEnv(specAuthorEnv); ok && strings.TrimSpace(envAuthor) != "" {
|
||||
return parseSpecAuthor(envAuthor)
|
||||
}
|
||||
|
||||
return specAuthor{}, nil
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseSpecAuthor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
want specAuthor
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid author",
|
||||
raw: "Test Author <author@example.invalid>",
|
||||
want: specAuthor{Name: "Test Author", Email: "author@example.invalid"},
|
||||
},
|
||||
{
|
||||
name: "missing email",
|
||||
raw: "Test Author",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing name",
|
||||
raw: "<author@example.invalid>",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty email",
|
||||
raw: "Test Author <>",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "multiline author",
|
||||
raw: "Test Author <author@example.invalid>\nOther <other@example.invalid>",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parseSpecAuthor(tt.raw)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("parseSpecAuthor() succeeded, want error")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("parseSpecAuthor(): %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("parseSpecAuthor() = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSpecAuthorUsesExplicitAuthor(t *testing.T) {
|
||||
t.Setenv(specAuthorEnv, "Env Author <env@example.invalid>")
|
||||
|
||||
got, err := resolveSpecAuthor("Test Author <author@example.invalid>")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveSpecAuthor(): %v", err)
|
||||
}
|
||||
want := specAuthor{Name: "Test Author", Email: "author@example.invalid"}
|
||||
if got != want {
|
||||
t.Fatalf("resolveSpecAuthor() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSpecAuthorUsesEnvAuthor(t *testing.T) {
|
||||
t.Setenv(specAuthorEnv, "Test Author <author@example.invalid>")
|
||||
|
||||
got, err := resolveSpecAuthor("")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveSpecAuthor(): %v", err)
|
||||
}
|
||||
want := specAuthor{Name: "Test Author", Email: "author@example.invalid"}
|
||||
if got != want {
|
||||
t.Fatalf("resolveSpecAuthor() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSpecAuthorAllowsMissingAuthor(t *testing.T) {
|
||||
t.Setenv(specAuthorEnv, "")
|
||||
|
||||
got, err := resolveSpecAuthor("")
|
||||
if err != nil {
|
||||
t.Fatalf("resolveSpecAuthor(): %v", err)
|
||||
}
|
||||
if !got.IsZero() {
|
||||
t.Fatalf("resolveSpecAuthor() = %#v, want zero author", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSpecAuthorRejectsInvalidEnvAuthor(t *testing.T) {
|
||||
t.Setenv(specAuthorEnv, "Test Author")
|
||||
|
||||
if _, err := resolveSpecAuthor(""); err == nil {
|
||||
t.Fatalf("resolveSpecAuthor() succeeded with invalid env author, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSPDXHeaderIncludesContributor(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writeSPDXHeader(&buf, specAuthor{Name: "Test Author", Email: "author@example.invalid"})
|
||||
|
||||
want := "# SPDX-FileContributor: Test Author <author@example.invalid>\n"
|
||||
if !strings.Contains(buf.String(), want) {
|
||||
t.Fatalf("writeSPDXHeader() missing %q in:\n%s", want, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteSPDXHeaderOmitsEmptyContributor(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writeSPDXHeader(&buf, specAuthor{})
|
||||
|
||||
if strings.Contains(buf.String(), "SPDX-FileContributor") {
|
||||
t.Fatalf("writeSPDXHeader() included empty contributor:\n%s", buf.String())
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -38,8 +38,9 @@ func shortCommitHash(hash string) string {
|
||||
|
||||
// pkgVersionFromGit determines the actual version to be packaged
|
||||
// from the git repository status and user preference.
|
||||
// Besides returning the upstream version, the "upstream" struct fields
|
||||
// u.version, u.commitID, u.commitIsh, u.hasRelease and u.isRelease are also set.
|
||||
// Besides returning the upstream version, the "upstream" struct
|
||||
// struct fields u.version, u.commitIsh, u.hasRelease and u.isRelease
|
||||
// are also set.
|
||||
// `preferredRev` should be empty if there are no user preferences.
|
||||
// TODO: also support other VCS
|
||||
func pkgVersionFromGit(gitdir string, u *upstream, preferredRev string, forcePrerelease bool) (string, error) {
|
||||
@@ -128,10 +129,9 @@ func pkgVersionFromGit(gitdir string, u *upstream, preferredRev string, forcePre
|
||||
}
|
||||
fullCommitHash := strings.TrimSpace(string(fullCommitHashBytes))
|
||||
lastCommitHash := shortCommitHash(fullCommitHash)
|
||||
u.commitID = fullCommitHash
|
||||
u.commitIsh = lastCommitHash
|
||||
|
||||
// Snapshot versions are based on the packaging date, not the commit date.
|
||||
u.version = fmt.Sprintf("0+git%s.%s", packagingDateString(), lastCommitHash)
|
||||
u.version = fmt.Sprintf("0+git%s.%s\n%%define commit_id %s", packagingDateString(), lastCommitHash, fullCommitHash)
|
||||
return u.version, nil
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# pack Command
|
||||
|
||||
[](../zh/pack.zh.md)
|
||||
[](update.en.md)
|
||||
|
||||
`pack` generates an RPM spec draft and a source archive from a Go import path.
|
||||
|
||||
**This command is intended to produce a maintainable starting point, not a final package that can be submitted directly. Always review the generated spec manually before submitting it to openRuyi or using it for builds.**
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
go2spec pack [FLAG]... <go-package-importpath>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
go2spec pack golang.org/x/net
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
```text
|
||||
-git_revision <rev>
|
||||
Check out a specific git revision, such as a tag, branch, or commit
|
||||
|
||||
-force_prerelease
|
||||
Package HEAD or tip instead of preferring the latest tagged version
|
||||
|
||||
-type <type>
|
||||
Select the package type, supported values are library, program, library+program, and program+library
|
||||
|
||||
-program_package_name <name>
|
||||
Override the program package name, and the source package name when appropriate
|
||||
|
||||
-allow_unknown_hoster
|
||||
Allow package-name generation for unknown hosters, Review the generated name manually
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
A successful run creates these files in the current directory:
|
||||
|
||||
- `<package-name>/`, package working directory
|
||||
- `<package-name>/<package-name>.spec`, generated RPM spec draft
|
||||
- `<package-name>_<version>.orig.tar.<gz|xz>`, upstream source archive
|
||||
|
||||
If the target package directory already exists and is not empty, go2spec aborts to avoid overwriting existing work.
|
||||
|
||||
## Package Types
|
||||
|
||||
- `library`, for Go library modules
|
||||
- `program`, for command packages whose root package is `main`
|
||||
- `library+program`, for projects that ship both library files and command subpackages
|
||||
- `program+library`, for program-first projects that also need a library subpackage
|
||||
|
||||
When `-type` is omitted, go2spec uses pkg.go.dev package metadata and local `go list` output to infer the type.
|
||||
@@ -1,46 +0,0 @@
|
||||
# update Command
|
||||
|
||||
[](../zh/update.zh.md)
|
||||
[](pack.en.md)
|
||||
|
||||
`update` scans existing Go spec packages, checks the latest upstream version, and can update the spec version.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
go2spec update [options] <SPECS_DIR>
|
||||
```
|
||||
|
||||
`<SPECS_DIR>` must contain `go-*` subdirectories with spec files.
|
||||
|
||||
```text
|
||||
SPECS_DIR/
|
||||
go-golang-x-net/
|
||||
go-golang-x-net/go-golang-x-net.spec
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```text
|
||||
-n
|
||||
Dry run, detect updates only, do not modify files
|
||||
|
||||
-j <num>
|
||||
Number of concurrent checks, default is 3
|
||||
```
|
||||
|
||||
## Recommended Workflow
|
||||
|
||||
Start with dry run mode:
|
||||
|
||||
```bash
|
||||
go2spec update -n /path/to/SPECS
|
||||
```
|
||||
|
||||
Apply updates after reviewing the output:
|
||||
|
||||
```bash
|
||||
go2spec update -j 4 /path/to/SPECS
|
||||
```
|
||||
|
||||
Review the generated diff before committing packaging changes.
|
||||
@@ -1,58 +0,0 @@
|
||||
# pack 命令
|
||||
|
||||
[](../en/pack.en.md)
|
||||
[](update.zh.md)
|
||||
|
||||
`pack` 根据 Go import path 生成 RPM spec 初稿和源码归档。
|
||||
|
||||
**该命令用于生成可继续维护的起点,不是最终可直接提交的包,提交到 openRuyi 或用于构建前必须人工 review 生成的 spec。**
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
go2spec pack [FLAG]... <go-package-importpath>
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
go2spec pack golang.org/x/net
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
```text
|
||||
-git_revision <rev>
|
||||
checkout 指定 git revision,例如 tag、branch 或 commit
|
||||
|
||||
-force_prerelease
|
||||
打包 HEAD 或 tip,而不是优先选择最新 tag
|
||||
|
||||
-type <type>
|
||||
选择包类型,支持 library、program、library+program 和 program+library
|
||||
|
||||
-program_package_name <name>
|
||||
覆盖程序包名,适用时也覆盖源包名
|
||||
|
||||
-allow_unknown_hoster
|
||||
允许未知 hoster 参与包名生成,生成结果必须人工检查
|
||||
```
|
||||
|
||||
## 输出
|
||||
|
||||
执行成功后会在当前目录创建:
|
||||
|
||||
- `<package-name>/`,package 工作目录
|
||||
- `<package-name>/<package-name>.spec`,生成的 RPM spec 初稿
|
||||
- `<package-name>_<version>.orig.tar.<gz|xz>`,上游源码归档
|
||||
|
||||
如果目标 package 目录已存在且非空,go2spec 会停止执行,以避免覆盖已有工作。
|
||||
|
||||
## 包类型
|
||||
|
||||
- `library`,适合 Go library module
|
||||
- `program`,适合根 package 为 `main` 的命令包
|
||||
- `library+program`,适合同时发布库文件和命令子包的项目
|
||||
- `program+library`,适合以程序为主,同时需要库子包的项目
|
||||
|
||||
不传 `-type` 时,go2spec 会根据 pkg.go.dev package 元数据和本地 `go list` 输出推断类型。
|
||||
@@ -1,46 +0,0 @@
|
||||
# update 命令
|
||||
|
||||
[](../en/update.en.md)
|
||||
[](pack.zh.md)
|
||||
|
||||
`update` 扫描已有 Golang spec 包,检查上游最新版本,并可更新 spec 版本。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
go2spec update [options] <SPECS_DIR>
|
||||
```
|
||||
|
||||
`<SPECS_DIR>` 必须包含带 spec 文件的 `go-*` 子目录。
|
||||
|
||||
```text
|
||||
SPECS_DIR/
|
||||
go-golang-x-net/
|
||||
go-golang-x-net/go-golang-x-net.spec
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
```text
|
||||
-n
|
||||
dry run,只检测更新,不修改文件
|
||||
|
||||
-j <num>
|
||||
并发检查数量,默认 3
|
||||
```
|
||||
|
||||
## 推荐流程
|
||||
|
||||
先执行 dry run:
|
||||
|
||||
```bash
|
||||
go2spec update -n /path/to/SPECS
|
||||
```
|
||||
|
||||
确认输出后再执行更新:
|
||||
|
||||
```bash
|
||||
go2spec update -j 4 /path/to/SPECS
|
||||
```
|
||||
|
||||
提交 packaging 变更前必须 review 生成的 diff。
|
||||
@@ -38,7 +38,6 @@ type upstream struct {
|
||||
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
|
||||
commitID string // full commit hash for commit-pinned snapshot builds
|
||||
tag string // Latest upstream tag, if any
|
||||
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
|
||||
@@ -328,7 +327,7 @@ func moduleUsesRepoSubdir(modulePath, repoURL string) bool {
|
||||
// the upstream version maps cleanly to %{commit_id} or %{version}.
|
||||
func (u *upstream) sourceRefForSpec() (string, error) {
|
||||
ref := u.tag
|
||||
if u.commitID != "" {
|
||||
if strings.Contains(u.version, "commit_id") {
|
||||
ref = "%{commit_id}"
|
||||
} else if u.isRelease && u.tag == "v"+u.version {
|
||||
ref = "v%{version}"
|
||||
@@ -1001,6 +1000,12 @@ func mainPack(args []string, usage func()) {
|
||||
"Override the program package name, and the source package name too\n"+
|
||||
"when appropriate, e.g. to name github.com/cli/cli as \"gh\"")
|
||||
|
||||
var authorString string
|
||||
flagSet.StringVar(&authorString,
|
||||
"author",
|
||||
"",
|
||||
"Set optional SPDX-FileContributor author in Name <email> format, defaulting to GO2SPEC_AUTHOR when set")
|
||||
|
||||
var allowUnknownHoster bool
|
||||
flagSet.BoolVar(&allowUnknownHoster,
|
||||
"allow_unknown_hoster",
|
||||
@@ -1027,6 +1032,10 @@ func mainPack(args []string, usage func()) {
|
||||
}
|
||||
|
||||
gitRevision = strings.TrimSpace(gitRevision)
|
||||
author, err := resolveSpecAuthor(authorString)
|
||||
if err != nil {
|
||||
log.Fatalf("resolve author: %v", err)
|
||||
}
|
||||
gopkg := flagSet.Arg(0)
|
||||
|
||||
// Remove URL scheme if present (https://, http://, git://, etc.)
|
||||
@@ -1145,7 +1154,7 @@ func mainPack(args []string, usage func()) {
|
||||
}
|
||||
|
||||
if err := writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, u.version,
|
||||
pkgType, u); err != nil {
|
||||
pkgType, u, author); err != nil {
|
||||
log.Fatalf("Could not create spec file: %v\n", err)
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -358,7 +358,10 @@ func pkgsiteLicenseExpression(licenses []pkgsiteLicense) string {
|
||||
types = append(types, typ)
|
||||
}
|
||||
sort.Strings(types)
|
||||
group := strings.Join(types, " OR ")
|
||||
group := types[0]
|
||||
if len(types) > 1 {
|
||||
group = "(" + strings.Join(types, " OR ") + ")"
|
||||
}
|
||||
seenGroups[group] = true
|
||||
}
|
||||
if len(seenGroups) == 0 {
|
||||
@@ -370,12 +373,5 @@ func pkgsiteLicenseExpression(licenses []pkgsiteLicense) string {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
sort.Strings(groups)
|
||||
if len(groups) > 1 {
|
||||
for i, group := range groups {
|
||||
if strings.Contains(group, " OR ") {
|
||||
groups[i] = "(" + group + ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(groups, " AND ")
|
||||
}
|
||||
|
||||
+6
-22
@@ -48,15 +48,7 @@ func TestPkgsiteLicenseExpression(t *testing.T) {
|
||||
licenses: []pkgsiteLicense{
|
||||
{FilePath: "LICENSE", Types: []string{"MIT", "Apache-2.0"}},
|
||||
},
|
||||
want: "Apache-2.0 OR MIT",
|
||||
},
|
||||
{
|
||||
name: "parenthesizes OR group when joined with AND",
|
||||
licenses: []pkgsiteLicense{
|
||||
{FilePath: "LICENSE", Types: []string{"MIT", "Apache-2.0"}},
|
||||
{FilePath: "COPYING", Types: []string{"BSD-3-Clause"}},
|
||||
},
|
||||
want: "(Apache-2.0 OR MIT) AND BSD-3-Clause",
|
||||
want: "(Apache-2.0 OR MIT)",
|
||||
},
|
||||
{
|
||||
name: "uses TODO when pkgsite has no SPDX type",
|
||||
@@ -436,9 +428,8 @@ func TestGetPkgsitePackageRetriesAmbiguousPathWithLongestModule(t *testing.T) {
|
||||
|
||||
func TestSourceURLForSpecUsesCommitIDMacro(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/example/project",
|
||||
version: "0+git20260522.abcdef",
|
||||
commitID: "0123456789abcdef",
|
||||
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 {
|
||||
@@ -503,9 +494,8 @@ func TestSourceURLForSpecUsesModuleProxyForRepoSubmodule(t *testing.T) {
|
||||
|
||||
func TestSourceURLForSpecRejectsCommitIDForRepoSubmodule(t *testing.T) {
|
||||
u := upstream{
|
||||
repoURL: "https://github.com/charmbracelet/x",
|
||||
version: "0+git20260522.abcdef",
|
||||
commitID: "abcdef1234567890",
|
||||
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 {
|
||||
@@ -539,7 +529,7 @@ func TestPkgVersionFromGitUsesPackagingDateAndSevenCharHash(t *testing.T) {
|
||||
}, "commit", "-m", "initial")
|
||||
|
||||
fullHash := strings.TrimSpace(runGit(t, dir, nil, "rev-parse", "HEAD"))
|
||||
want := "0+git20250808." + fullHash[:7]
|
||||
want := "0+git20250808." + fullHash[:7] + "\n%define commit_id " + fullHash
|
||||
|
||||
u := upstream{}
|
||||
got, err := pkgVersionFromGit(dir, &u, "", false)
|
||||
@@ -549,12 +539,6 @@ func TestPkgVersionFromGitUsesPackagingDateAndSevenCharHash(t *testing.T) {
|
||||
if got != want {
|
||||
t.Fatalf("pkgVersionFromGit() = %q, want %q", got, want)
|
||||
}
|
||||
if strings.Contains(got, "\n") || strings.Contains(got, "commit_id") {
|
||||
t.Fatalf("pkgVersionFromGit() returned metadata in version: %q", got)
|
||||
}
|
||||
if u.commitID != fullHash {
|
||||
t.Fatalf("commitID = %q, want %q", u.commitID, fullHash)
|
||||
}
|
||||
if strings.Contains(got, "19990102") {
|
||||
t.Fatalf("pkgVersionFromGit() used commit date: %q", got)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -71,7 +72,7 @@ func getSpecAssetFilesForGopkg(gopkg string) (specAssetFiles, error) {
|
||||
}
|
||||
|
||||
func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version string,
|
||||
pkgType packageType, u *upstream) error {
|
||||
pkgType packageType, u *upstream, author specAuthor) error {
|
||||
|
||||
f, err := os.Create(filepath.Join(dir, "", openRuyiSrc+".spec"))
|
||||
if err != nil {
|
||||
@@ -111,19 +112,19 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
|
||||
// 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, "#\n")
|
||||
fmt.Fprintf(f, "# SPDX-License-Identifier: MulanPSL-2.0\n")
|
||||
fmt.Fprintf(f, "\n")
|
||||
writeSPDXHeader(f, author)
|
||||
|
||||
// Macros
|
||||
fmt.Fprintf(f, "%%define _name %s\n", upstreamName)
|
||||
fmt.Fprintf(f, "%%define go_import_path %s\n", gopkg)
|
||||
|
||||
if u.commitID != "" {
|
||||
fmt.Fprintf(f, "%%define commit_id %s\n", u.commitID)
|
||||
// If pkgVersionFromGit embedded a commit_id define in u.version, extract and write it here
|
||||
commitRe := regexp.MustCompile(`%define\s+commit_id\s+([0-9a-fA-F]+)`)
|
||||
if m := commitRe.FindStringSubmatch(u.version); m != nil {
|
||||
fmt.Fprintf(f, "%%define commit_id %s\n", m[1])
|
||||
// Remove the embedded %define line from version so it doesn't get written to Version: field
|
||||
version = commitRe.ReplaceAllString(version, "")
|
||||
version = strings.TrimSpace(version)
|
||||
}
|
||||
fmt.Fprintf(f, "\n")
|
||||
|
||||
@@ -196,18 +197,17 @@ func writeSpec(dir, gopkg, openRuyiSrc, openRuyiLib, openRuyiProgram, version st
|
||||
log.Fatalf("Invalid pkgType %d in writeRPMSpec(), aborting", pkgType)
|
||||
}
|
||||
|
||||
if pkgType != typeLibrary {
|
||||
fmt.Fprintf(f, "\n")
|
||||
}
|
||||
fmt.Fprintf(f, "\n")
|
||||
fmt.Fprintf(f, "%%description\n")
|
||||
fmt.Fprintf(f, "%s\n\n", strings.TrimRight(longdescription, "\n"))
|
||||
fmt.Fprintf(f, "%s\n", longdescription)
|
||||
fmt.Fprintf(f, "\n")
|
||||
|
||||
// %files
|
||||
writeRPMFilesSection(f, openRuyiSrc, openRuyiLib, openRuyiProgram, pkgType, assetFiles)
|
||||
|
||||
// %changelog
|
||||
fmt.Fprintf(f, "%%changelog\n")
|
||||
fmt.Fprintf(f, "%%autochangelog\n\n")
|
||||
fmt.Fprintf(f, "%%{?autochangelog}\n\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -360,3 +360,14 @@ func convertDependenciesToRPM(goPkgs []string) []string {
|
||||
|
||||
return rpmDeps
|
||||
}
|
||||
|
||||
func writeSPDXHeader(w io.Writer, author specAuthor) {
|
||||
fmt.Fprintf(w, "# SPDX-FileCopyrightText: (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS)\n")
|
||||
fmt.Fprintf(w, "# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors\n")
|
||||
if !author.IsZero() {
|
||||
fmt.Fprintf(w, "# SPDX-FileContributor: %s\n", author.String())
|
||||
}
|
||||
fmt.Fprintf(w, "#\n")
|
||||
fmt.Fprintf(w, "# SPDX-License-Identifier: MulanPSL-2.0\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
|
||||
@@ -295,7 +295,42 @@ func writeSpecLines(path string, lines []string) error {
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
func updateSpecVersion(spec *specInfo, newVersion, remoteSHA256 string, updateRemoteAsset bool) error {
|
||||
func ensureSpecContributor(lines []string, author specAuthor) ([]string, error) {
|
||||
if author.IsZero() {
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
contributor := "# SPDX-FileContributor: " + author.String()
|
||||
lastContributorIndex := -1
|
||||
licenseIndex := -1
|
||||
for i, line := range lines {
|
||||
if line == contributor {
|
||||
return lines, nil
|
||||
}
|
||||
if strings.HasPrefix(line, "# SPDX-FileContributor:") {
|
||||
lastContributorIndex = i
|
||||
}
|
||||
if licenseIndex < 0 && strings.HasPrefix(line, "# SPDX-License-Identifier:") {
|
||||
licenseIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
insertIndex := 0
|
||||
if lastContributorIndex >= 0 {
|
||||
insertIndex = lastContributorIndex + 1
|
||||
} else if licenseIndex > 0 && strings.TrimSpace(lines[licenseIndex-1]) == "#" {
|
||||
insertIndex = licenseIndex - 1
|
||||
} else if licenseIndex >= 0 {
|
||||
insertIndex = licenseIndex
|
||||
}
|
||||
|
||||
lines = append(lines, "")
|
||||
copy(lines[insertIndex+1:], lines[insertIndex:])
|
||||
lines[insertIndex] = contributor
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func updateSpecVersion(spec *specInfo, newVersion, remoteSHA256 string, updateRemoteAsset bool, author specAuthor) error {
|
||||
lines, err := readSpecLines(spec.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -329,6 +364,10 @@ func updateSpecVersion(spec *specInfo, newVersion, remoteSHA256 string, updateRe
|
||||
return fmt.Errorf("Version line not found in %s", spec.FilePath)
|
||||
}
|
||||
if !updateRemoteAsset {
|
||||
lines, err = ensureSpecContributor(lines, author)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeSpecLines(spec.FilePath, lines)
|
||||
}
|
||||
if source0RemoteAssetIndex >= 0 {
|
||||
@@ -346,6 +385,11 @@ func updateSpecVersion(spec *specInfo, newVersion, remoteSHA256 string, updateRe
|
||||
lines = append(lines[:source0Index], append([]string{insert}, lines[source0Index:]...)...)
|
||||
}
|
||||
|
||||
lines, err = ensureSpecContributor(lines, author)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return writeSpecLines(spec.FilePath, lines)
|
||||
}
|
||||
|
||||
@@ -354,7 +398,7 @@ type downloadResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func verifyAndUpdateSpec(ctx context.Context, spec *specInfo, dryRun bool) error {
|
||||
func verifyAndUpdateSpec(ctx context.Context, spec *specInfo, dryRun bool, author specAuthor) error {
|
||||
err := verifyDownload(ctx, spec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("download verification failed: %w", err)
|
||||
@@ -372,7 +416,7 @@ func verifyAndUpdateSpec(ctx context.Context, spec *specInfo, dryRun bool) error
|
||||
return fmt.Errorf("compute remote asset sha256: %w", err)
|
||||
}
|
||||
|
||||
if err := updateSpecVersion(spec, newVer, remoteSHA256, updateRemoteAsset); err != nil {
|
||||
if err := updateSpecVersion(spec, newVer, remoteSHA256, updateRemoteAsset, author); err != nil {
|
||||
return fmt.Errorf("update spec file: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -380,7 +424,7 @@ func verifyAndUpdateSpec(ctx context.Context, spec *specInfo, dryRun bool) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func processUpdate(ctx context.Context, spec *specInfo, dryRun bool) error {
|
||||
func processUpdate(ctx context.Context, spec *specInfo, dryRun bool, author specAuthor) error {
|
||||
err := verifyDownload(ctx, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -398,7 +442,7 @@ func processUpdate(ctx context.Context, spec *specInfo, dryRun bool) error {
|
||||
return fmt.Errorf("compute remote asset sha256: %w", err)
|
||||
}
|
||||
|
||||
if err := updateSpecVersion(spec, newVer, remoteSHA256, updateRemoteAsset); err != nil {
|
||||
if err := updateSpecVersion(spec, newVer, remoteSHA256, updateRemoteAsset, author); err != nil {
|
||||
return fmt.Errorf("update spec file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -312,6 +312,7 @@ func mainUpdate(args []string) int {
|
||||
fs.SetOutput(os.Stderr)
|
||||
concurrency := fs.Int("j", 3, "number of concurrent checks")
|
||||
dryRun := fs.Bool("n", false, "dry run: detect updates only, do not modify files")
|
||||
authorString := fs.String("author", "", "Set optional SPDX-FileContributor author in Name <email> format, defaulting to GO2SPEC_AUTHOR when set")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintf(fs.Output(), "Usage: %s update [options] <SPECS_DIR>\n\n", os.Args[0])
|
||||
fmt.Fprintf(fs.Output(), "Check Go module spec files for updates.\n\n")
|
||||
@@ -389,13 +390,20 @@ func mainUpdate(args []string) int {
|
||||
sp.Error = err.Error()
|
||||
out.print(fmt.Sprintf(" FAIL: %s - %v", sp.GoImportPath, err))
|
||||
atomic.AddInt32(&c.upErrors, 1)
|
||||
} else if err := updateSpecVersion(sp, newVer, remoteSHA256, updateRemoteAsset); err != nil {
|
||||
sp.Error = err.Error()
|
||||
out.print(fmt.Sprintf(" FAIL: %s - %v", sp.GoImportPath, err))
|
||||
atomic.AddInt32(&c.upErrors, 1)
|
||||
} else {
|
||||
atomic.AddInt32(&c.modified, 1)
|
||||
out.print(fmt.Sprintf(" OK: %s updated to %s", sp.GoImportPath, newVer))
|
||||
author, err := resolveSpecAuthor(*authorString)
|
||||
if err != nil {
|
||||
sp.Error = err.Error()
|
||||
out.print(fmt.Sprintf(" FAIL: %s - %v", sp.GoImportPath, err))
|
||||
atomic.AddInt32(&c.upErrors, 1)
|
||||
} else if err := updateSpecVersion(sp, newVer, remoteSHA256, updateRemoteAsset, author); err != nil {
|
||||
sp.Error = err.Error()
|
||||
out.print(fmt.Sprintf(" FAIL: %s - %v", sp.GoImportPath, err))
|
||||
atomic.AddInt32(&c.upErrors, 1)
|
||||
} else {
|
||||
atomic.AddInt32(&c.modified, 1)
|
||||
out.print(fmt.Sprintf(" OK: %s updated to %s", sp.GoImportPath, newVer))
|
||||
}
|
||||
}
|
||||
}
|
||||
atomic.AddInt32(&c.processed, 1)
|
||||
|
||||
+189
-5
@@ -15,6 +15,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testSpecAuthor = specAuthor{Name: "Test Author", Email: "author@example.invalid"}
|
||||
|
||||
func TestUpdateSpecVersionPreservesFormatting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -32,7 +34,7 @@ func TestUpdateSpecVersionPreservesFormatting(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", strings.Repeat("a", 64), true); err != nil {
|
||||
if err := updateSpecVersion(spec, "2.0.0", strings.Repeat("a", 64), true, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ func TestUpdateSpecVersionReturnsErrorWhenVersionLineMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", strings.Repeat("a", 64), true); err == nil {
|
||||
if err := updateSpecVersion(spec, "2.0.0", strings.Repeat("a", 64), true, testSpecAuthor); err == nil {
|
||||
t.Fatal("expected updateSpecVersion to fail when Version line is missing")
|
||||
}
|
||||
}
|
||||
@@ -223,7 +225,7 @@ func TestUpdateSpecVersionOnlyUpdatesSource0RemoteAsset(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", source0Hash, true); err != nil {
|
||||
if err := updateSpecVersion(spec, "2.0.0", source0Hash, true, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
@@ -258,7 +260,7 @@ func TestUpdateSpecVersionClearsRemoteAssetWhenHashEmpty(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", true); err != nil {
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", true, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
@@ -296,7 +298,7 @@ func TestUpdateSpecVersionKeepsRemoteAssetWhenUpdateDisabled(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false); err != nil {
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
@@ -314,6 +316,188 @@ func TestUpdateSpecVersionKeepsRemoteAssetWhenUpdateDisabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionDoesNotDuplicateExistingContributor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
contributor := "# SPDX-FileContributor: " + testSpecAuthor.String()
|
||||
original := strings.Join([]string{
|
||||
"# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors",
|
||||
contributor,
|
||||
"# SPDX-License-Identifier: MulanPSL-2.0",
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
if count := strings.Count(string(updated), contributor); count != 1 {
|
||||
t.Fatalf("contributor count = %d, want 1:\n%s", count, string(updated))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionAppendsDifferentContributor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
original := strings.Join([]string{
|
||||
"# SPDX-FileContributor: Existing Author <existing@example.invalid>",
|
||||
"# SPDX-License-Identifier: MulanPSL-2.0",
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
want := "# SPDX-FileContributor: Existing Author <existing@example.invalid>\n# SPDX-FileContributor: " + testSpecAuthor.String() + "\n# SPDX-License-Identifier:"
|
||||
if !strings.Contains(string(updated), want) {
|
||||
t.Fatalf("contributor was not appended after existing contributor:\n%s", string(updated))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionInsertsContributorBeforeSeparator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
original := strings.Join([]string{
|
||||
"# SPDX-FileCopyrightText: (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS)",
|
||||
"# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors",
|
||||
"#",
|
||||
"# SPDX-License-Identifier: MulanPSL-2.0",
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
want := "# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors\n# SPDX-FileContributor: " + testSpecAuthor.String() + "\n#\n# SPDX-License-Identifier:"
|
||||
if !strings.Contains(string(updated), want) {
|
||||
t.Fatalf("contributor was not inserted before separator:\n%s", string(updated))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionInsertsContributorBeforeLicense(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
original := strings.Join([]string{
|
||||
"# SPDX-FileCopyrightText: (C) 2026 openRuyi Project Contributors",
|
||||
"# SPDX-License-Identifier: MulanPSL-2.0",
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
want := "# SPDX-FileContributor: " + testSpecAuthor.String() + "\n# SPDX-License-Identifier:"
|
||||
if !strings.Contains(string(updated), want) {
|
||||
t.Fatalf("contributor was not inserted before license:\n%s", string(updated))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionInsertsContributorAtStartWithoutHeader(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
original := strings.Join([]string{
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, testSpecAuthor); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
wantPrefix := "# SPDX-FileContributor: " + testSpecAuthor.String() + "\nName:"
|
||||
if !strings.HasPrefix(string(updated), wantPrefix) {
|
||||
t.Fatalf("contributor was not inserted at start:\n%s", string(updated))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSpecVersionOmitsMissingContributorAuthor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
specPath := filepath.Join(dir, "test.spec")
|
||||
original := strings.Join([]string{
|
||||
"Name: golang-test",
|
||||
"Version: 1.0.0",
|
||||
}, "\n")
|
||||
if err := os.WriteFile(specPath, []byte(original), 0o644); err != nil {
|
||||
t.Fatalf("write spec: %v", err)
|
||||
}
|
||||
|
||||
spec := &specInfo{FilePath: specPath}
|
||||
if err := updateSpecVersion(spec, "2.0.0", "", false, specAuthor{}); err != nil {
|
||||
t.Fatalf("updateSpecVersion: %v", err)
|
||||
}
|
||||
|
||||
updated, err := os.ReadFile(specPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
got := string(updated)
|
||||
if !strings.Contains(got, "Version: 2.0.0") {
|
||||
t.Fatalf("version was not updated:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "SPDX-FileContributor") {
|
||||
t.Fatalf("empty author should not add contributor:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvedSource0URLSkipsPinnedCommit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user