Skip to content

Commit c709fa1

Browse files
authored
1 parent 0a6f635 commit c709fa1

File tree

22 files changed

+1353
-2
lines changed

22 files changed

+1353
-2
lines changed

custom/conf/app.example.ini

+2
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,8 @@ ROUTER = console
25162516
;LIMIT_SIZE_PYPI = -1
25172517
;; Maximum size of a RubyGems upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
25182518
;LIMIT_SIZE_RUBYGEMS = -1
2519+
;; Maximum size of a Swift upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
2520+
;LIMIT_SIZE_SWIFT = -1
25192521
;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
25202522
;LIMIT_SIZE_VAGRANT = -1
25212523

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
12541254
- `LIMIT_SIZE_PUB`: **-1**: Maximum size of a Pub upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12551255
- `LIMIT_SIZE_PYPI`: **-1**: Maximum size of a PyPI upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12561256
- `LIMIT_SIZE_RUBYGEMS`: **-1**: Maximum size of a RubyGems upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
1257+
- `LIMIT_SIZE_SWIFT`: **-1**: Maximum size of a Swift upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12571258
- `LIMIT_SIZE_VAGRANT`: **-1**: Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
12581259

12591260
## Mirror (`mirror`)

docs/content/doc/packages/overview.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ The following package managers are currently supported:
4040
| [Pub]({{< relref "doc/packages/pub.en-us.md" >}}) | Dart | `dart`, `flutter` |
4141
| [PyPI]({{< relref "doc/packages/pypi.en-us.md" >}}) | Python | `pip`, `twine` |
4242
| [RubyGems]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Ruby | `gem`, `Bundler` |
43+
| [Swift]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Swift | `swift` |
4344
| [Vagrant]({{< relref "doc/packages/vagrant.en-us.md" >}}) | - | `vagrant` |
4445

4546
**The following paragraphs only apply if Packages are not globally disabled!**
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
date: "2023-01-10T00:00:00+00:00"
3+
title: "Swift Packages Repository"
4+
slug: "packages/swift"
5+
draft: false
6+
toc: false
7+
menu:
8+
sidebar:
9+
parent: "packages"
10+
name: "Swift"
11+
weight: 95
12+
identifier: "swift"
13+
---
14+
15+
# Swift Packages Repository
16+
17+
Publish [Swift](hhttps://www.swift.org/) packages for your user or organization.
18+
19+
**Table of Contents**
20+
21+
{{< toc >}}
22+
23+
## Requirements
24+
25+
To work with the Swift package registry, you need to use [swift](https://www.swift.org/getting-started/) to consume and a HTTP client (like `curl`) to publish packages.
26+
27+
## Configuring the package registry
28+
29+
To register the package registry and provide credentials, execute:
30+
31+
```shell
32+
swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password}
33+
```
34+
35+
| Placeholder | Description |
36+
| ------------ | ----------- |
37+
| `owner` | The owner of the package. |
38+
| `username` | Your Gitea username. |
39+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
40+
41+
The login is optional and only needed if the package registry is private.
42+
43+
## Publish a package
44+
45+
First you have to pack the contents of your package:
46+
47+
```shell
48+
swift package archive-source
49+
```
50+
51+
To publish the package perform a HTTP PUT request with the package content in the request body.
52+
53+
```shell --user your_username:your_password_or_token \
54+
curl -X PUT --user {username}:{password} \
55+
-H "Accept: application/vnd.swift.registry.v1+json" \
56+
-F source-archive=@/path/to/package.zip \
57+
-F metadata={metadata} \
58+
https://gitea.example.com/api/packages/{owner}/swift/{scope}/{name}/{version}
59+
```
60+
61+
| Placeholder | Description |
62+
| ----------- | ----------- |
63+
| `username` | Your Gitea username. |
64+
| `password` | Your Gitea password. If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. |
65+
| `owner` | The owner of the package. |
66+
| `scope` | The package scope. |
67+
| `name` | The package name. |
68+
| `version` | The package version. |
69+
| `metadata` | (Optional) The metadata of the package. JSON encoded subset of https://schema.org/SoftwareSourceCode |
70+
71+
You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.
72+
73+
## Install a package
74+
75+
To install a Swift package from the package registry, add it in the `Package.swift` file dependencies list:
76+
77+
```
78+
dependencies: [
79+
.package(id: "{scope}.{name}", from:"{version}")
80+
]
81+
```
82+
83+
| Parameter | Description |
84+
| ----------- | ----------- |
85+
| `scope` | The package scope. |
86+
| `name` | The package name. |
87+
| `version` | The package version. |
88+
89+
Afterwards execute the following command to install it:
90+
91+
```shell
92+
swift package resolve
93+
```

models/packages/descriptor.go

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"code.gitea.io/gitea/modules/packages/pub"
2525
"code.gitea.io/gitea/modules/packages/pypi"
2626
"code.gitea.io/gitea/modules/packages/rubygems"
27+
"code.gitea.io/gitea/modules/packages/swift"
2728
"code.gitea.io/gitea/modules/packages/vagrant"
2829

2930
"github.com/hashicorp/go-version"
@@ -159,6 +160,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
159160
metadata = &pypi.Metadata{}
160161
case TypeRubyGems:
161162
metadata = &rubygems.Metadata{}
163+
case TypeSwift:
164+
metadata = &swift.Metadata{}
162165
case TypeVagrant:
163166
metadata = &vagrant.Metadata{}
164167
default:

models/packages/package.go

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const (
4444
TypePub Type = "pub"
4545
TypePyPI Type = "pypi"
4646
TypeRubyGems Type = "rubygems"
47+
TypeSwift Type = "swift"
4748
TypeVagrant Type = "vagrant"
4849
)
4950

@@ -62,6 +63,7 @@ var TypeList = []Type{
6263
TypePub,
6364
TypePyPI,
6465
TypeRubyGems,
66+
TypeSwift,
6567
TypeVagrant,
6668
}
6769

@@ -96,6 +98,8 @@ func (pt Type) Name() string {
9698
return "PyPI"
9799
case TypeRubyGems:
98100
return "RubyGems"
101+
case TypeSwift:
102+
return "Swift"
99103
case TypeVagrant:
100104
return "Vagrant"
101105
}
@@ -133,6 +137,8 @@ func (pt Type) SVGName() string {
133137
return "gitea-python"
134138
case TypeRubyGems:
135139
return "gitea-rubygems"
140+
case TypeSwift:
141+
return "gitea-swift"
136142
case TypeVagrant:
137143
return "gitea-vagrant"
138144
}

modules/packages/swift/metadata.go

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package swift
5+
6+
import (
7+
"archive/zip"
8+
"fmt"
9+
"io"
10+
"path"
11+
"regexp"
12+
"strings"
13+
14+
"code.gitea.io/gitea/modules/json"
15+
"code.gitea.io/gitea/modules/util"
16+
"code.gitea.io/gitea/modules/validation"
17+
18+
"github.com/hashicorp/go-version"
19+
)
20+
21+
var (
22+
ErrMissingManifestFile = util.NewInvalidArgumentErrorf("Package.swift file is missing")
23+
ErrManifestFileTooLarge = util.NewInvalidArgumentErrorf("Package.swift file is too large")
24+
ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid")
25+
26+
manifestPattern = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`)
27+
toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`)
28+
)
29+
30+
const (
31+
maxManifestFileSize = 128 * 1024
32+
33+
PropertyScope = "swift.scope"
34+
PropertyName = "swift.name"
35+
PropertyRepositoryURL = "swift.repository_url"
36+
)
37+
38+
// Package represents a Swift package
39+
type Package struct {
40+
RepositoryURLs []string
41+
Metadata *Metadata
42+
}
43+
44+
// Metadata represents the metadata of a Swift package
45+
type Metadata struct {
46+
Description string `json:"description,omitempty"`
47+
Keywords []string `json:"keywords,omitempty"`
48+
RepositoryURL string `json:"repository_url,omitempty"`
49+
License string `json:"license,omitempty"`
50+
Author Person `json:"author,omitempty"`
51+
Manifests map[string]*Manifest `json:"manifests,omitempty"`
52+
}
53+
54+
// Manifest represents a Package.swift file
55+
type Manifest struct {
56+
Content string `json:"content"`
57+
ToolsVersion string `json:"tools_version,omitempty"`
58+
}
59+
60+
// https://schema.org/SoftwareSourceCode
61+
type SoftwareSourceCode struct {
62+
Context []string `json:"@context"`
63+
Type string `json:"@type"`
64+
Name string `json:"name"`
65+
Version string `json:"version"`
66+
Description string `json:"description,omitempty"`
67+
Keywords []string `json:"keywords,omitempty"`
68+
CodeRepository string `json:"codeRepository,omitempty"`
69+
License string `json:"license,omitempty"`
70+
Author Person `json:"author"`
71+
ProgrammingLanguage ProgrammingLanguage `json:"programmingLanguage"`
72+
RepositoryURLs []string `json:"repositoryURLs,omitempty"`
73+
}
74+
75+
// https://schema.org/ProgrammingLanguage
76+
type ProgrammingLanguage struct {
77+
Type string `json:"@type"`
78+
Name string `json:"name"`
79+
URL string `json:"url"`
80+
}
81+
82+
// https://schema.org/Person
83+
type Person struct {
84+
Type string `json:"@type,omitempty"`
85+
GivenName string `json:"givenName,omitempty"`
86+
MiddleName string `json:"middleName,omitempty"`
87+
FamilyName string `json:"familyName,omitempty"`
88+
}
89+
90+
func (p Person) String() string {
91+
var sb strings.Builder
92+
if p.GivenName != "" {
93+
sb.WriteString(p.GivenName)
94+
}
95+
if p.MiddleName != "" {
96+
if sb.Len() > 0 {
97+
sb.WriteRune(' ')
98+
}
99+
sb.WriteString(p.MiddleName)
100+
}
101+
if p.FamilyName != "" {
102+
if sb.Len() > 0 {
103+
sb.WriteRune(' ')
104+
}
105+
sb.WriteString(p.FamilyName)
106+
}
107+
return sb.String()
108+
}
109+
110+
// ParsePackage parses the Swift package upload
111+
func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
112+
zr, err := zip.NewReader(sr, size)
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
p := &Package{
118+
Metadata: &Metadata{
119+
Manifests: make(map[string]*Manifest),
120+
},
121+
}
122+
123+
for _, file := range zr.File {
124+
manifestMatch := manifestPattern.FindStringSubmatch(path.Base(file.Name))
125+
if len(manifestMatch) == 0 {
126+
continue
127+
}
128+
129+
if file.UncompressedSize64 > maxManifestFileSize {
130+
return nil, ErrManifestFileTooLarge
131+
}
132+
133+
f, err := zr.Open(file.Name)
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
content, err := io.ReadAll(f)
139+
140+
if err := f.Close(); err != nil {
141+
return nil, err
142+
}
143+
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
swiftVersion := ""
149+
if len(manifestMatch) == 2 && manifestMatch[1] != "" {
150+
v, err := version.NewSemver(manifestMatch[1])
151+
if err != nil {
152+
return nil, ErrInvalidManifestVersion
153+
}
154+
swiftVersion = TrimmedVersionString(v)
155+
}
156+
157+
manifest := &Manifest{
158+
Content: string(content),
159+
}
160+
161+
toolsMatch := toolsVersionPattern.FindStringSubmatch(manifest.Content)
162+
if len(toolsMatch) == 2 {
163+
v, err := version.NewSemver(toolsMatch[1])
164+
if err != nil {
165+
return nil, ErrInvalidManifestVersion
166+
}
167+
168+
manifest.ToolsVersion = TrimmedVersionString(v)
169+
}
170+
171+
p.Metadata.Manifests[swiftVersion] = manifest
172+
}
173+
174+
if _, found := p.Metadata.Manifests[""]; !found {
175+
return nil, ErrMissingManifestFile
176+
}
177+
178+
if mr != nil {
179+
var ssc *SoftwareSourceCode
180+
if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
181+
return nil, err
182+
}
183+
184+
p.Metadata.Description = ssc.Description
185+
p.Metadata.Keywords = ssc.Keywords
186+
p.Metadata.License = ssc.License
187+
p.Metadata.Author = Person{
188+
GivenName: ssc.Author.GivenName,
189+
MiddleName: ssc.Author.MiddleName,
190+
FamilyName: ssc.Author.FamilyName,
191+
}
192+
193+
p.Metadata.RepositoryURL = ssc.CodeRepository
194+
if !validation.IsValidURL(p.Metadata.RepositoryURL) {
195+
p.Metadata.RepositoryURL = ""
196+
}
197+
198+
p.RepositoryURLs = ssc.RepositoryURLs
199+
}
200+
201+
return p, nil
202+
}
203+
204+
// TrimmedVersionString returns the version string without the patch segment if it is zero
205+
func TrimmedVersionString(v *version.Version) string {
206+
segments := v.Segments64()
207+
208+
var b strings.Builder
209+
fmt.Fprintf(&b, "%d.%d", segments[0], segments[1])
210+
if segments[2] != 0 {
211+
fmt.Fprintf(&b, ".%d", segments[2])
212+
}
213+
return b.String()
214+
}

0 commit comments

Comments
 (0)