Skip to content

Commit 0c3c041

Browse files
KN4CK3Rdancheg97ExplodingDragonwxiaoguang
authored
Add Arch package registry (#32692)
Close #25037 Close #31037 This PR adds a Arch package registry usable with pacman. ![grafik](https://github.com/user-attachments/assets/81cdb0c2-02f9-4733-bee2-e48af6b45224) Rewrite of #25396 and #31037. You can follow [this tutorial](https://wiki.archlinux.org/title/Creating_packages) to build a package for testing. Docs PR: https://gitea.com/gitea/docs/pulls/111 Co-authored-by: [[email protected]](mailto:[email protected]) Co-authored-by: @ExplodingDragon --------- Co-authored-by: dancheg97 <[email protected]> Co-authored-by: dragon <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 5ab7aa7 commit 0c3c041

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1687
-91
lines changed

models/packages/arch/search.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package arch
5+
6+
import (
7+
"context"
8+
9+
packages_model "code.gitea.io/gitea/models/packages"
10+
arch_module "code.gitea.io/gitea/modules/packages/arch"
11+
)
12+
13+
// GetRepositories gets all available repositories
14+
func GetRepositories(ctx context.Context, ownerID int64) ([]string, error) {
15+
return packages_model.GetDistinctPropertyValues(
16+
ctx,
17+
packages_model.TypeArch,
18+
ownerID,
19+
packages_model.PropertyTypeFile,
20+
arch_module.PropertyRepository,
21+
nil,
22+
)
23+
}
24+
25+
// GetArchitectures gets all available architectures for the given repository
26+
func GetArchitectures(ctx context.Context, ownerID int64, repository string) ([]string, error) {
27+
return packages_model.GetDistinctPropertyValues(
28+
ctx,
29+
packages_model.TypeArch,
30+
ownerID,
31+
packages_model.PropertyTypeFile,
32+
arch_module.PropertyArchitecture,
33+
&packages_model.DistinctPropertyDependency{
34+
Name: arch_module.PropertyRepository,
35+
Value: repository,
36+
},
37+
)
38+
}

models/packages/descriptor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
user_model "code.gitea.io/gitea/models/user"
1414
"code.gitea.io/gitea/modules/json"
1515
"code.gitea.io/gitea/modules/packages/alpine"
16+
"code.gitea.io/gitea/modules/packages/arch"
1617
"code.gitea.io/gitea/modules/packages/cargo"
1718
"code.gitea.io/gitea/modules/packages/chef"
1819
"code.gitea.io/gitea/modules/packages/composer"
@@ -150,6 +151,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
150151
switch p.Type {
151152
case TypeAlpine:
152153
metadata = &alpine.VersionMetadata{}
154+
case TypeArch:
155+
metadata = &arch.VersionMetadata{}
153156
case TypeCargo:
154157
metadata = &cargo.Metadata{}
155158
case TypeChef:

models/packages/package.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Type string
3131
// List of supported packages
3232
const (
3333
TypeAlpine Type = "alpine"
34+
TypeArch Type = "arch"
3435
TypeCargo Type = "cargo"
3536
TypeChef Type = "chef"
3637
TypeComposer Type = "composer"
@@ -55,6 +56,7 @@ const (
5556

5657
var TypeList = []Type{
5758
TypeAlpine,
59+
TypeArch,
5860
TypeCargo,
5961
TypeChef,
6062
TypeComposer,
@@ -82,6 +84,8 @@ func (pt Type) Name() string {
8284
switch pt {
8385
case TypeAlpine:
8486
return "Alpine"
87+
case TypeArch:
88+
return "Arch"
8589
case TypeCargo:
8690
return "Cargo"
8791
case TypeChef:
@@ -131,6 +135,8 @@ func (pt Type) SVGName() string {
131135
switch pt {
132136
case TypeAlpine:
133137
return "gitea-alpine"
138+
case TypeArch:
139+
return "gitea-arch"
134140
case TypeCargo:
135141
return "gitea-cargo"
136142
case TypeChef:

models/packages/package_file.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,11 @@ func SearchFiles(ctx context.Context, opts *PackageFileSearchOptions) ([]*Packag
221221
return pfs, count, err
222222
}
223223

224+
// HasFiles tests if there are files of packages matching the search options
225+
func HasFiles(ctx context.Context, opts *PackageFileSearchOptions) (bool, error) {
226+
return db.Exist[PackageFile](ctx, opts.toConds())
227+
}
228+
224229
// CalculateFileSize sums up all blob sizes matching the search options.
225230
// It does NOT respect the deduplication of blobs.
226231
func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {

modules/packages/arch/metadata.go

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package arch
5+
6+
import (
7+
"archive/tar"
8+
"bufio"
9+
"bytes"
10+
"compress/gzip"
11+
"io"
12+
"regexp"
13+
"strconv"
14+
"strings"
15+
16+
"code.gitea.io/gitea/modules/util"
17+
"code.gitea.io/gitea/modules/validation"
18+
19+
"github.com/klauspost/compress/zstd"
20+
"github.com/ulikunitz/xz"
21+
)
22+
23+
const (
24+
PropertyRepository = "arch.repository"
25+
PropertyArchitecture = "arch.architecture"
26+
PropertySignature = "arch.signature"
27+
PropertyMetadata = "arch.metadata"
28+
29+
SettingKeyPrivate = "arch.key.private"
30+
SettingKeyPublic = "arch.key.public"
31+
32+
RepositoryPackage = "_arch"
33+
RepositoryVersion = "_repository"
34+
35+
AnyArch = "any"
36+
)
37+
38+
var (
39+
ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf(".PKGINFO file is missing")
40+
ErrUnsupportedFormat = util.NewInvalidArgumentErrorf("unsupported package container format")
41+
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
42+
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
43+
ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
44+
45+
// https://man.archlinux.org/man/PKGBUILD.5
46+
namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
47+
versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
48+
)
49+
50+
type Package struct {
51+
Name string
52+
Version string
53+
VersionMetadata VersionMetadata
54+
FileMetadata FileMetadata
55+
FileCompressionExtension string
56+
}
57+
58+
type VersionMetadata struct {
59+
Description string `json:"description,omitempty"`
60+
ProjectURL string `json:"project_url,omitempty"`
61+
Licenses []string `json:"licenses,omitempty"`
62+
}
63+
64+
type FileMetadata struct {
65+
Architecture string `json:"architecture"`
66+
Base string `json:"base,omitempty"`
67+
InstalledSize int64 `json:"installed_size,omitempty"`
68+
BuildDate int64 `json:"build_date,omitempty"`
69+
Packager string `json:"packager,omitempty"`
70+
Groups []string `json:"groups,omitempty"`
71+
Provides []string `json:"provides,omitempty"`
72+
Depends []string `json:"depends,omitempty"`
73+
OptDepends []string `json:"opt_depends,omitempty"`
74+
MakeDepends []string `json:"make_depends,omitempty"`
75+
CheckDepends []string `json:"check_depends,omitempty"`
76+
XData []string `json:"xdata,omitempty"`
77+
Backup []string `json:"backup,omitempty"`
78+
Files []string `json:"files,omitempty"`
79+
}
80+
81+
// ParsePackage parses an Arch package file
82+
func ParsePackage(r io.Reader) (*Package, error) {
83+
header := make([]byte, 10)
84+
n, err := util.ReadAtMost(r, header)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
r = io.MultiReader(bytes.NewReader(header[:n]), r)
90+
91+
var inner io.Reader
92+
var compressionType string
93+
if bytes.HasPrefix(header, []byte{0x28, 0xB5, 0x2F, 0xFD}) { // zst
94+
zr, err := zstd.NewReader(r)
95+
if err != nil {
96+
return nil, err
97+
}
98+
defer zr.Close()
99+
100+
inner = zr
101+
compressionType = "zst"
102+
} else if bytes.HasPrefix(header, []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A}) { // xz
103+
xzr, err := xz.NewReader(r)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
inner = xzr
109+
compressionType = "xz"
110+
} else if bytes.HasPrefix(header, []byte{0x1F, 0x8B}) { // gz
111+
gzr, err := gzip.NewReader(r)
112+
if err != nil {
113+
return nil, err
114+
}
115+
defer gzr.Close()
116+
117+
inner = gzr
118+
compressionType = "gz"
119+
} else {
120+
return nil, ErrUnsupportedFormat
121+
}
122+
123+
var p *Package
124+
files := make([]string, 0, 10)
125+
126+
tr := tar.NewReader(inner)
127+
for {
128+
hd, err := tr.Next()
129+
if err == io.EOF {
130+
break
131+
}
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
if hd.Typeflag != tar.TypeReg {
137+
continue
138+
}
139+
140+
filename := hd.FileInfo().Name()
141+
if filename == ".PKGINFO" {
142+
p, err = ParsePackageInfo(tr)
143+
if err != nil {
144+
return nil, err
145+
}
146+
} else if !strings.HasPrefix(filename, ".") {
147+
files = append(files, hd.Name)
148+
}
149+
}
150+
151+
if p == nil {
152+
return nil, ErrMissingPKGINFOFile
153+
}
154+
155+
p.FileMetadata.Files = files
156+
p.FileCompressionExtension = compressionType
157+
158+
return p, nil
159+
}
160+
161+
// ParsePackageInfo parses a .PKGINFO file to retrieve the metadata
162+
// https://man.archlinux.org/man/PKGBUILD.5
163+
// https://gitlab.archlinux.org/pacman/pacman/-/blob/master/lib/libalpm/be_package.c#L161
164+
func ParsePackageInfo(r io.Reader) (*Package, error) {
165+
p := &Package{}
166+
167+
s := bufio.NewScanner(r)
168+
for s.Scan() {
169+
line := s.Text()
170+
171+
if strings.HasPrefix(line, "#") {
172+
continue
173+
}
174+
175+
i := strings.IndexRune(line, '=')
176+
if i == -1 {
177+
continue
178+
}
179+
180+
key := strings.TrimSpace(line[:i])
181+
value := strings.TrimSpace(line[i+1:])
182+
183+
switch key {
184+
case "pkgname":
185+
p.Name = value
186+
case "pkgbase":
187+
p.FileMetadata.Base = value
188+
case "pkgver":
189+
p.Version = value
190+
case "pkgdesc":
191+
p.VersionMetadata.Description = value
192+
case "url":
193+
p.VersionMetadata.ProjectURL = value
194+
case "packager":
195+
p.FileMetadata.Packager = value
196+
case "arch":
197+
p.FileMetadata.Architecture = value
198+
case "license":
199+
p.VersionMetadata.Licenses = append(p.VersionMetadata.Licenses, value)
200+
case "provides":
201+
p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
202+
case "depend":
203+
p.FileMetadata.Depends = append(p.FileMetadata.Depends, value)
204+
case "optdepend":
205+
p.FileMetadata.OptDepends = append(p.FileMetadata.OptDepends, value)
206+
case "makedepend":
207+
p.FileMetadata.MakeDepends = append(p.FileMetadata.MakeDepends, value)
208+
case "checkdepend":
209+
p.FileMetadata.CheckDepends = append(p.FileMetadata.CheckDepends, value)
210+
case "backup":
211+
p.FileMetadata.Backup = append(p.FileMetadata.Backup, value)
212+
case "group":
213+
p.FileMetadata.Groups = append(p.FileMetadata.Groups, value)
214+
case "builddate":
215+
date, err := strconv.ParseInt(value, 10, 64)
216+
if err != nil {
217+
return nil, err
218+
}
219+
p.FileMetadata.BuildDate = date
220+
case "size":
221+
size, err := strconv.ParseInt(value, 10, 64)
222+
if err != nil {
223+
return nil, err
224+
}
225+
p.FileMetadata.InstalledSize = size
226+
case "xdata":
227+
p.FileMetadata.XData = append(p.FileMetadata.XData, value)
228+
}
229+
}
230+
if err := s.Err(); err != nil {
231+
return nil, err
232+
}
233+
234+
if !namePattern.MatchString(p.Name) {
235+
return nil, ErrInvalidName
236+
}
237+
if !versionPattern.MatchString(p.Version) {
238+
return nil, ErrInvalidVersion
239+
}
240+
if p.FileMetadata.Architecture == "" {
241+
return nil, ErrInvalidArchitecture
242+
}
243+
244+
if !validation.IsValidURL(p.VersionMetadata.ProjectURL) {
245+
p.VersionMetadata.ProjectURL = ""
246+
}
247+
248+
return p, nil
249+
}

0 commit comments

Comments
 (0)