summaryrefslogtreecommitdiffstats
path: root/modules/packages/conan
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
commitdd136858f1ea40ad3c94191d647487fa4f31926c (patch)
tree58fec94a7b2a12510c9664b21793f1ed560c6518 /modules/packages/conan
parentInitial commit. (diff)
downloadforgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.tar.xz
forgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.zip
Adding upstream version 9.0.0.upstream/9.0.0upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'modules/packages/conan')
-rw-r--r--modules/packages/conan/conanfile_parser.go67
-rw-r--r--modules/packages/conan/conanfile_parser_test.go51
-rw-r--r--modules/packages/conan/conaninfo_parser.go123
-rw-r--r--modules/packages/conan/conaninfo_parser_test.go85
-rw-r--r--modules/packages/conan/metadata.go23
-rw-r--r--modules/packages/conan/reference.go155
-rw-r--r--modules/packages/conan/reference_test.go148
7 files changed, 652 insertions, 0 deletions
diff --git a/modules/packages/conan/conanfile_parser.go b/modules/packages/conan/conanfile_parser.go
new file mode 100644
index 0000000..c47b242
--- /dev/null
+++ b/modules/packages/conan/conanfile_parser.go
@@ -0,0 +1,67 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "io"
+ "regexp"
+ "strings"
+)
+
+var (
+ patternAuthor = compilePattern("author")
+ patternHomepage = compilePattern("homepage")
+ patternURL = compilePattern("url")
+ patternLicense = compilePattern("license")
+ patternDescription = compilePattern("description")
+ patternTopics = regexp.MustCompile(`(?im)^\s*topics\s*=\s*\((.+)\)`)
+ patternTopicList = regexp.MustCompile(`\s*['"](.+?)['"]\s*,?`)
+)
+
+func compilePattern(name string) *regexp.Regexp {
+ return regexp.MustCompile(`(?im)^\s*` + name + `\s*=\s*['"\(](.+)['"\)]`)
+}
+
+func ParseConanfile(r io.Reader) (*Metadata, error) {
+ buf, err := io.ReadAll(io.LimitReader(r, 1<<20))
+ if err != nil {
+ return nil, err
+ }
+
+ metadata := &Metadata{}
+
+ m := patternAuthor.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ metadata.Author = string(m[1])
+ }
+ m = patternHomepage.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ metadata.ProjectURL = string(m[1])
+ }
+ m = patternURL.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ metadata.RepositoryURL = string(m[1])
+ }
+ m = patternLicense.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ metadata.License = strings.ReplaceAll(strings.ReplaceAll(string(m[1]), "'", ""), "\"", "")
+ }
+ m = patternDescription.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ metadata.Description = string(m[1])
+ }
+ m = patternTopics.FindSubmatch(buf)
+ if len(m) > 1 && len(m[1]) > 0 {
+ m2 := patternTopicList.FindAllSubmatch(m[1], -1)
+ if len(m2) > 0 {
+ metadata.Keywords = make([]string, 0, len(m2))
+ for _, g := range m2 {
+ if len(g) > 1 {
+ metadata.Keywords = append(metadata.Keywords, string(g[1]))
+ }
+ }
+ }
+ }
+ return metadata, nil
+}
diff --git a/modules/packages/conan/conanfile_parser_test.go b/modules/packages/conan/conanfile_parser_test.go
new file mode 100644
index 0000000..fe867fb
--- /dev/null
+++ b/modules/packages/conan/conanfile_parser_test.go
@@ -0,0 +1,51 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const (
+ name = "ConanPackage"
+ version = "1.2"
+ license = "MIT"
+ author = "Gitea <info@gitea.io>"
+ homepage = "https://gitea.io/"
+ url = "https://gitea.com/"
+ description = "Description of ConanPackage"
+ topic1 = "gitea"
+ topic2 = "conan"
+ contentConanfile = `from conans import ConanFile, CMake, tools
+
+class ConanPackageConan(ConanFile):
+ name = "` + name + `"
+ version = "` + version + `"
+ license = "` + license + `"
+ author = "` + author + `"
+ homepage = "` + homepage + `"
+ url = "` + url + `"
+ description = "` + description + `"
+ topics = ("` + topic1 + `", "` + topic2 + `")
+ settings = "os", "compiler", "build_type", "arch"
+ options = {"shared": [True, False], "fPIC": [True, False]}
+ default_options = {"shared": False, "fPIC": True}
+ generators = "cmake"
+`
+)
+
+func TestParseConanfile(t *testing.T) {
+ metadata, err := ParseConanfile(strings.NewReader(contentConanfile))
+ require.NoError(t, err)
+ assert.Equal(t, license, metadata.License)
+ assert.Equal(t, author, metadata.Author)
+ assert.Equal(t, homepage, metadata.ProjectURL)
+ assert.Equal(t, url, metadata.RepositoryURL)
+ assert.Equal(t, description, metadata.Description)
+ assert.Equal(t, []string{topic1, topic2}, metadata.Keywords)
+}
diff --git a/modules/packages/conan/conaninfo_parser.go b/modules/packages/conan/conaninfo_parser.go
new file mode 100644
index 0000000..de11dbe
--- /dev/null
+++ b/modules/packages/conan/conaninfo_parser.go
@@ -0,0 +1,123 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "bufio"
+ "io"
+ "strings"
+
+ "code.gitea.io/gitea/modules/util"
+)
+
+// Conaninfo represents infos of a Conan package
+type Conaninfo struct {
+ Settings map[string]string `json:"settings"`
+ FullSettings map[string]string `json:"full_settings"`
+ Requires []string `json:"requires"`
+ FullRequires []string `json:"full_requires"`
+ Options map[string]string `json:"options"`
+ FullOptions map[string]string `json:"full_options"`
+ RecipeHash string `json:"recipe_hash"`
+ Environment map[string][]string `json:"environment"`
+}
+
+func ParseConaninfo(r io.Reader) (*Conaninfo, error) {
+ sections, err := readSections(io.LimitReader(r, 1<<20))
+ if err != nil {
+ return nil, err
+ }
+
+ info := &Conaninfo{}
+ for section, lines := range sections {
+ if len(lines) == 0 {
+ continue
+ }
+ switch section {
+ case "settings":
+ info.Settings = toMap(lines)
+ case "full_settings":
+ info.FullSettings = toMap(lines)
+ case "options":
+ info.Options = toMap(lines)
+ case "full_options":
+ info.FullOptions = toMap(lines)
+ case "requires":
+ info.Requires = lines
+ case "full_requires":
+ info.FullRequires = lines
+ case "recipe_hash":
+ info.RecipeHash = lines[0]
+ case "env":
+ info.Environment = toMapArray(lines)
+ }
+ }
+ return info, nil
+}
+
+func readSections(r io.Reader) (map[string][]string, error) {
+ sections := make(map[string][]string)
+
+ section := ""
+ lines := make([]string, 0, 5)
+
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+ if section != "" {
+ sections[section] = lines
+ }
+ section = line[1 : len(line)-1]
+ lines = make([]string, 0, 5)
+ continue
+ }
+ if section != "" {
+ if line != "" {
+ lines = append(lines, line)
+ }
+ continue
+ }
+ if line != "" {
+ return nil, util.NewInvalidArgumentErrorf("invalid conaninfo.txt")
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+ if section != "" {
+ sections[section] = lines
+ }
+ return sections, nil
+}
+
+func toMap(lines []string) map[string]string {
+ result := make(map[string]string)
+ for _, line := range lines {
+ parts := strings.SplitN(line, "=", 2)
+ if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
+ continue
+ }
+ result[parts[0]] = parts[1]
+ }
+ return result
+}
+
+func toMapArray(lines []string) map[string][]string {
+ result := make(map[string][]string)
+ for _, line := range lines {
+ parts := strings.SplitN(line, "=", 2)
+ if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
+ continue
+ }
+ var items []string
+ if strings.HasPrefix(parts[1], "[") && strings.HasSuffix(parts[1], "]") {
+ items = strings.Split(parts[1], ",")
+ } else {
+ items = []string{parts[1]}
+ }
+ result[parts[0]] = items
+ }
+ return result
+}
diff --git a/modules/packages/conan/conaninfo_parser_test.go b/modules/packages/conan/conaninfo_parser_test.go
new file mode 100644
index 0000000..dfb1836
--- /dev/null
+++ b/modules/packages/conan/conaninfo_parser_test.go
@@ -0,0 +1,85 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+const (
+ settingsKey = "arch"
+ settingsValue = "x84_64"
+ optionsKey = "shared"
+ optionsValue = "False"
+ requires = "fmt/7.1.3"
+ hash = "74714915a51073acb548ca1ce29afbac"
+ envKey = "CC"
+ envValue = "gcc-10"
+
+ contentConaninfo = `[settings]
+ ` + settingsKey + `=` + settingsValue + `
+
+[requires]
+ ` + requires + `
+
+[options]
+ ` + optionsKey + `=` + optionsValue + `
+
+[full_settings]
+ ` + settingsKey + `=` + settingsValue + `
+
+[full_requires]
+ ` + requires + `
+
+[full_options]
+ ` + optionsKey + `=` + optionsValue + `
+
+[recipe_hash]
+ ` + hash + `
+
+[env]
+` + envKey + `=` + envValue + `
+
+`
+)
+
+func TestParseConaninfo(t *testing.T) {
+ info, err := ParseConaninfo(strings.NewReader(contentConaninfo))
+ assert.NotNil(t, info)
+ require.NoError(t, err)
+ assert.Equal(
+ t,
+ map[string]string{
+ settingsKey: settingsValue,
+ },
+ info.Settings,
+ )
+ assert.Equal(t, info.Settings, info.FullSettings)
+ assert.Equal(
+ t,
+ map[string]string{
+ optionsKey: optionsValue,
+ },
+ info.Options,
+ )
+ assert.Equal(t, info.Options, info.FullOptions)
+ assert.Equal(
+ t,
+ []string{requires},
+ info.Requires,
+ )
+ assert.Equal(t, info.Requires, info.FullRequires)
+ assert.Equal(t, hash, info.RecipeHash)
+ assert.Equal(
+ t,
+ map[string][]string{
+ envKey: {envValue},
+ },
+ info.Environment,
+ )
+}
diff --git a/modules/packages/conan/metadata.go b/modules/packages/conan/metadata.go
new file mode 100644
index 0000000..256a376
--- /dev/null
+++ b/modules/packages/conan/metadata.go
@@ -0,0 +1,23 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+const (
+ PropertyRecipeUser = "conan.recipe.user"
+ PropertyRecipeChannel = "conan.recipe.channel"
+ PropertyRecipeRevision = "conan.recipe.revision"
+ PropertyPackageReference = "conan.package.reference"
+ PropertyPackageRevision = "conan.package.revision"
+ PropertyPackageInfo = "conan.package.info"
+)
+
+// Metadata represents the metadata of a Conan package
+type Metadata struct {
+ Author string `json:"author,omitempty"`
+ License string `json:"license,omitempty"`
+ ProjectURL string `json:"project_url,omitempty"`
+ RepositoryURL string `json:"repository_url,omitempty"`
+ Description string `json:"description,omitempty"`
+ Keywords []string `json:"keywords,omitempty"`
+}
diff --git a/modules/packages/conan/reference.go b/modules/packages/conan/reference.go
new file mode 100644
index 0000000..58f268b
--- /dev/null
+++ b/modules/packages/conan/reference.go
@@ -0,0 +1,155 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
+)
+
+const (
+ // taken from https://github.com/conan-io/conan/blob/develop/conans/model/ref.py
+ minChars = 2
+ maxChars = 51
+
+ // DefaultRevision if no revision is specified
+ DefaultRevision = "0"
+)
+
+var (
+ namePattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{%d,%d}$`, minChars-1, maxChars-1))
+ revisionPattern = regexp.MustCompile(fmt.Sprintf(`^[a-zA-Z0-9]{1,%d}$`, maxChars))
+
+ ErrValidation = util.NewInvalidArgumentErrorf("could not validate one or more reference fields")
+)
+
+// RecipeReference represents a recipe <Name>/<Version>@<User>/<Channel>#<Revision>
+type RecipeReference struct {
+ Name string
+ Version string
+ User string
+ Channel string
+ Revision string
+}
+
+func NewRecipeReference(name, version, user, channel, revision string) (*RecipeReference, error) {
+ log.Trace("Conan Recipe: %s/%s(@%s/%s(#%s))", name, version, user, channel, revision)
+
+ if user == "_" {
+ user = ""
+ }
+ if channel == "_" {
+ channel = ""
+ }
+
+ if (user != "" && channel == "") || (user == "" && channel != "") {
+ return nil, ErrValidation
+ }
+
+ if !namePattern.MatchString(name) {
+ return nil, ErrValidation
+ }
+
+ v := strings.TrimSpace(version)
+ if v == "" {
+ return nil, ErrValidation
+ }
+ if user != "" && !namePattern.MatchString(user) {
+ return nil, ErrValidation
+ }
+ if channel != "" && !namePattern.MatchString(channel) {
+ return nil, ErrValidation
+ }
+ if revision != "" && !revisionPattern.MatchString(revision) {
+ return nil, ErrValidation
+ }
+
+ return &RecipeReference{name, v, user, channel, revision}, nil
+}
+
+func (r *RecipeReference) RevisionOrDefault() string {
+ if r.Revision == "" {
+ return DefaultRevision
+ }
+ return r.Revision
+}
+
+func (r *RecipeReference) String() string {
+ rev := ""
+ if r.Revision != "" {
+ rev = "#" + r.Revision
+ }
+ if r.User == "" || r.Channel == "" {
+ return fmt.Sprintf("%s/%s%s", r.Name, r.Version, rev)
+ }
+ return fmt.Sprintf("%s/%s@%s/%s%s", r.Name, r.Version, r.User, r.Channel, rev)
+}
+
+func (r *RecipeReference) LinkName() string {
+ user := r.User
+ if user == "" {
+ user = "_"
+ }
+ channel := r.Channel
+ if channel == "" {
+ channel = "_"
+ }
+ return fmt.Sprintf("%s/%s/%s/%s/%s", r.Name, r.Version, user, channel, r.RevisionOrDefault())
+}
+
+func (r *RecipeReference) WithRevision(revision string) *RecipeReference {
+ return &RecipeReference{r.Name, r.Version, r.User, r.Channel, revision}
+}
+
+// AsKey builds the additional key for the package file
+func (r *RecipeReference) AsKey() string {
+ return fmt.Sprintf("%s|%s|%s", r.User, r.Channel, r.RevisionOrDefault())
+}
+
+// PackageReference represents a package of a recipe <Name>/<Version>@<User>/<Channel>#<Revision> <Reference>#<Revision>
+type PackageReference struct {
+ Recipe *RecipeReference
+ Reference string
+ Revision string
+}
+
+func NewPackageReference(recipe *RecipeReference, reference, revision string) (*PackageReference, error) {
+ log.Trace("Conan Package: %v %s(#%s)", recipe, reference, revision)
+
+ if recipe == nil {
+ return nil, ErrValidation
+ }
+ if reference == "" || !revisionPattern.MatchString(reference) {
+ return nil, ErrValidation
+ }
+ if revision != "" && !revisionPattern.MatchString(revision) {
+ return nil, ErrValidation
+ }
+
+ return &PackageReference{recipe, reference, revision}, nil
+}
+
+func (r *PackageReference) RevisionOrDefault() string {
+ if r.Revision == "" {
+ return DefaultRevision
+ }
+ return r.Revision
+}
+
+func (r *PackageReference) LinkName() string {
+ return fmt.Sprintf("%s/%s", r.Reference, r.RevisionOrDefault())
+}
+
+func (r *PackageReference) WithRevision(revision string) *PackageReference {
+ return &PackageReference{r.Recipe, r.Reference, revision}
+}
+
+// AsKey builds the additional key for the package file
+func (r *PackageReference) AsKey() string {
+ return fmt.Sprintf("%s|%s|%s|%s|%s", r.Recipe.User, r.Recipe.Channel, r.Recipe.RevisionOrDefault(), r.Reference, r.RevisionOrDefault())
+}
diff --git a/modules/packages/conan/reference_test.go b/modules/packages/conan/reference_test.go
new file mode 100644
index 0000000..7d39bd8
--- /dev/null
+++ b/modules/packages/conan/reference_test.go
@@ -0,0 +1,148 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package conan
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewRecipeReference(t *testing.T) {
+ cases := []struct {
+ Name string
+ Version string
+ User string
+ Channel string
+ Revision string
+ IsValid bool
+ }{
+ {"", "", "", "", "", false},
+ {"name", "", "", "", "", false},
+ {"", "1.0", "", "", "", false},
+ {"", "", "user", "", "", false},
+ {"", "", "", "channel", "", false},
+ {"", "", "", "", "0", false},
+ {"name", "1.0", "", "", "", true},
+ {"name", "1.0", "user", "", "", false},
+ {"name", "1.0", "", "channel", "", false},
+ {"name", "1.0", "user", "channel", "", true},
+ {"name", "1.0", "_", "", "", true},
+ {"name", "1.0", "", "_", "", true},
+ {"name", "1.0", "_", "_", "", true},
+ {"name", "1.0", "_", "_", "0", true},
+ {"name", "1.0", "", "", "0", true},
+ {"name", "1.0.0q", "", "", "0", true},
+ {"name", "1.0", "", "", "000000000000000000000000000000000000000000000000000000000000", false},
+ }
+
+ for i, c := range cases {
+ rref, err := NewRecipeReference(c.Name, c.Version, c.User, c.Channel, c.Revision)
+ if c.IsValid {
+ require.NoError(t, err, "case %d, should be invalid", i)
+ assert.NotNil(t, rref, "case %d, should not be nil", i)
+ } else {
+ require.Error(t, err, "case %d, should be valid", i)
+ }
+ }
+}
+
+func TestRecipeReferenceRevisionOrDefault(t *testing.T) {
+ rref, err := NewRecipeReference("name", "1.0", "", "", "")
+ require.NoError(t, err)
+ assert.Equal(t, DefaultRevision, rref.RevisionOrDefault())
+
+ rref, err = NewRecipeReference("name", "1.0", "", "", DefaultRevision)
+ require.NoError(t, err)
+ assert.Equal(t, DefaultRevision, rref.RevisionOrDefault())
+
+ rref, err = NewRecipeReference("name", "1.0", "", "", "Az09")
+ require.NoError(t, err)
+ assert.Equal(t, "Az09", rref.RevisionOrDefault())
+}
+
+func TestRecipeReferenceString(t *testing.T) {
+ rref, err := NewRecipeReference("name", "1.0", "", "", "")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0", rref.String())
+
+ rref, err = NewRecipeReference("name", "1.0", "user", "channel", "")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0@user/channel", rref.String())
+
+ rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0@user/channel#Az09", rref.String())
+}
+
+func TestRecipeReferenceLinkName(t *testing.T) {
+ rref, err := NewRecipeReference("name", "1.0", "", "", "")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0/_/_/0", rref.LinkName())
+
+ rref, err = NewRecipeReference("name", "1.0", "user", "channel", "")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0/user/channel/0", rref.LinkName())
+
+ rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09")
+ require.NoError(t, err)
+ assert.Equal(t, "name/1.0/user/channel/Az09", rref.LinkName())
+}
+
+func TestNewPackageReference(t *testing.T) {
+ rref, _ := NewRecipeReference("name", "1.0", "", "", "")
+
+ cases := []struct {
+ Recipe *RecipeReference
+ Reference string
+ Revision string
+ IsValid bool
+ }{
+ {nil, "", "", false},
+ {rref, "", "", false},
+ {nil, "aZ09", "", false},
+ {rref, "aZ09", "", true},
+ {rref, "", "Az09", false},
+ {rref, "aZ09", "Az09", true},
+ }
+
+ for i, c := range cases {
+ pref, err := NewPackageReference(c.Recipe, c.Reference, c.Revision)
+ if c.IsValid {
+ require.NoError(t, err, "case %d, should be invalid", i)
+ assert.NotNil(t, pref, "case %d, should not be nil", i)
+ } else {
+ require.Error(t, err, "case %d, should be valid", i)
+ }
+ }
+}
+
+func TestPackageReferenceRevisionOrDefault(t *testing.T) {
+ rref, _ := NewRecipeReference("name", "1.0", "", "", "")
+
+ pref, err := NewPackageReference(rref, "ref", "")
+ require.NoError(t, err)
+ assert.Equal(t, DefaultRevision, pref.RevisionOrDefault())
+
+ pref, err = NewPackageReference(rref, "ref", DefaultRevision)
+ require.NoError(t, err)
+ assert.Equal(t, DefaultRevision, pref.RevisionOrDefault())
+
+ pref, err = NewPackageReference(rref, "ref", "Az09")
+ require.NoError(t, err)
+ assert.Equal(t, "Az09", pref.RevisionOrDefault())
+}
+
+func TestPackageReferenceLinkName(t *testing.T) {
+ rref, _ := NewRecipeReference("name", "1.0", "", "", "")
+
+ pref, err := NewPackageReference(rref, "ref", "")
+ require.NoError(t, err)
+ assert.Equal(t, "ref/0", pref.LinkName())
+
+ pref, err = NewPackageReference(rref, "ref", "Az09")
+ require.NoError(t, err)
+ assert.Equal(t, "ref/Az09", pref.LinkName())
+}