Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fmt.Println(version) // 1.2.3

Type information is embedded in `types.json`. Type names and namespace requirements are checked against the pinned [purl-spec](https://github.com/package-url/purl-spec) submodule. Default registries and URL templates are maintained locally.

The local types `bioconductor`, `clojars`, `deno`, and `elm` are retained. The legacy `homebrew` type keeps its existing PURLs and registry behavior; use `brew` explicitly for the registered type. Recognition of a type does not imply full support for its normalization or validation rules.
The local types `bioconductor`, `clojars`, `deno`, and `elm` are retained. `bsd` follows the FreeBSD type proposed in [purl-spec#496](https://github.com/package-url/purl-spec/pull/496); the `freebsd` ecosystem maps to `pkg:bsd/freebsd/...`. The legacy `homebrew` type keeps its existing PURLs and registry behavior; use `brew` explicitly for the registered type. Recognition of a type does not imply full support for its normalization or validation rules.

```go
purl.KnownTypes() // []string of all known PURL types
Expand Down
9 changes: 7 additions & 2 deletions ecosystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
ecosystemAlpine = "alpine"
ecosystemArch = "arch"
ecosystemComposer = "composer"
ecosystemFreeBSD = "freebsd"
ecosystemGit = "git"
ecosystemGitHubActions = "github-actions"
ecosystemGolang = "golang"
Expand All @@ -19,13 +20,15 @@ const (
purlTypeGem = "gem"
purlTypeGitHubActions = "githubactions"
purlTypeVSCodeExtension = "vscode-extension"
purlTypeBSD = "bsd"
)

// purlTypeForEcosystem maps ecosystem names to PURL types.
// Most ecosystems use their name as the PURL type, but some differ.
var purlTypeForEcosystem = map[string]string{
ecosystemAlpine: "apk",
ecosystemArch: "alpm",
ecosystemFreeBSD: "bsd",
ecosystemRubyGems: purlTypeGem,
ecosystemPackagist: ecosystemComposer,
ecosystemGitHubActions: purlTypeGitHubActions,
Expand Down Expand Up @@ -72,8 +75,9 @@ var depsdevSystemNames = map[string]string{

// defaultNamespaces defines default namespaces for certain ecosystems.
var defaultNamespaces = map[string]string{
ecosystemAlpine: ecosystemAlpine,
ecosystemArch: ecosystemArch,
ecosystemAlpine: ecosystemAlpine,
ecosystemArch: ecosystemArch,
ecosystemFreeBSD: ecosystemFreeBSD,
}

// NormalizeEcosystem returns the canonical ecosystem name.
Expand Down Expand Up @@ -149,6 +153,7 @@ func PURLTypeToDepsdev(purlType string) string {
// - composer: vendor/package -> namespace="vendor", name="package"
// - alpine: pkg -> namespace="alpine", name="pkg"
// - arch: pkg -> namespace="arch", name="pkg"
// - freebsd: pkg -> namespace="freebsd", name="pkg"
// - swift: host/owner/package -> namespace="host/owner", name="package"
//
// Swift registry identities do not contain source repository coordinates and
Expand Down
41 changes: 40 additions & 1 deletion ecosystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestEcosystemToPURLType(t *testing.T) {
{"npm", "npm"},
{"alpine", "apk"},
{"arch", "alpm"},
{"freebsd", "bsd"},
{"rubygems", "gem"},
{"packagist", "composer"},
{"cargo", "cargo"},
Expand All @@ -65,6 +66,7 @@ func TestPURLTypeToEcosystem(t *testing.T) {
}{
{"apk", "alpine"},
{"alpm", "arch"},
{"bsd", "freebsd"},
{"gem", "rubygems"},
{"composer", "packagist"},
{"githubactions", "github-actions"},
Expand Down Expand Up @@ -210,6 +212,7 @@ func TestMakePURLString(t *testing.T) {
{"alpine default namespace", "alpine", "curl", "8.0.0", "pkg:apk/alpine/curl@8.0.0"},
{"alpine default namespace escaping", "alpine", "curl/ssl", "8.0.0-r0", "pkg:apk/alpine/curl%2Fssl@8.0.0-r0"},
{"arch default namespace", "arch", "base", "1.0", "pkg:alpm/arch/base@1.0"},
{"freebsd default namespace", "freebsd", "py311-requests", "2.32.3_1", "pkg:bsd/freebsd/py311-requests@2.32.3_1"},
{"github-actions", "github-actions", "actions/checkout", "v4", "pkg:githubactions/actions/checkout@v4"},
{"github-actions path", "github-actions", "actions/cache/restore", "v4", "pkg:githubactions/actions/cache@v4"},
{"cargo", "cargo", "serde", "1.0.0", "pkg:cargo/serde@1.0.0"},
Expand Down Expand Up @@ -280,7 +283,7 @@ func TestSupportedEcosystems(t *testing.T) {
has[e] = true
}

required := []string{"npm", "cargo", "pypi", "maven", "golang", "go", "gem", "alpine"}
required := []string{"npm", "cargo", "pypi", "maven", "golang", "go", "gem", "alpine", "freebsd"}
for _, r := range required {
if !has[r] {
t.Errorf("SupportedEcosystems() missing %q", r)
Expand All @@ -301,6 +304,7 @@ func TestIsValidEcosystem(t *testing.T) {
{"gem", true},
{"rubygems", true},
{"alpine", true},
{"freebsd", true},
{"notarealecosystem", false},
}

Expand All @@ -312,3 +316,38 @@ func TestIsValidEcosystem(t *testing.T) {
})
}
}

func TestFreeBSDPURL(t *testing.T) {
const s = "pkg:bsd/freebsd/emacs@30.1_2?distro=14.3&epoch=3"
p, err := Parse(s)
if err != nil {
t.Fatal(err)
}
if p.Type != "bsd" || p.Namespace != "freebsd" || p.Name != "emacs" || p.Version != "30.1_2" {
t.Errorf("Parse(%q) = %+v", s, p.PackageURL)
}
if got := p.String(); got != s {
t.Errorf("String() = %q, want %q", got, s)
}
if got := PURLTypeToEcosystem(p.Type); got != "freebsd" {
t.Errorf("PURLTypeToEcosystem(%q) = %q, want freebsd", p.Type, got)
}
if cfg := TypeInfo("bsd"); cfg == nil || !cfg.NamespaceRequired() {
t.Error("bsd type must require a namespace")
}
}

func TestFreeBSDNamespaceCase(t *testing.T) {
// The bsd namespace is case-insensitive and lowercased; the name keeps its case.
const want = "pkg:bsd/freebsd/p5-Mojolicious@9.39"
parsed, err := Parse("pkg:bsd/FreeBSD/p5-Mojolicious@9.39")
if err != nil {
t.Fatal(err)
}
if got := parsed.String(); got != want {
t.Errorf("Parse().String() = %q, want %q", got, want)
}
if got := New("bsd", "FreeBSD", "p5-Mojolicious", "9.39", nil).String(); got != want {
t.Errorf("New().String() = %q, want %q", got, want)
}
}
5 changes: 4 additions & 1 deletion purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ func Parse(s string) (*PURL, error) {
// lowercaseCaseInsensitive lowercases components the spec declares
// case-insensitive where packageurl-go's Normalize leaves them as given.
func lowercaseCaseInsensitive(p *packageurl.PackageURL) {
if p.Type == purlTypeVSCodeExtension {
switch p.Type {
case purlTypeVSCodeExtension:
p.Namespace = strings.ToLower(p.Namespace)
p.Name = strings.ToLower(p.Name)
case purlTypeBSD:
p.Namespace = strings.ToLower(p.Namespace)
}
}

Expand Down
1 change: 1 addition & 0 deletions spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestSpecTypeMetadata(t *testing.T) {

localTypes := map[string]string{
"bioconductor": "Bioconductor registry support",
"bsd": "FreeBSD packages, proposed in purl-spec#496",
"clojars": "Clojars registry support",
"deno": "Deno registry support",
"elm": "Elm registry support",
Expand Down
9 changes: 9 additions & 0 deletions types.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@
"pkg:brew/go"
]
},
"bsd": {
"description": "FreeBSD and other BSD operating system packages",
"default_registry": null,
"namespace_requirement": "required",
"examples": [
"pkg:bsd/freebsd/emacs@30.1_2?distro=14.3&epoch=3",
"pkg:bsd/freebsd/python311@3.11.13?distro=14.3"
]
},
"cargo": {
"description": "Cargo packages for Rust",
"default_registry": "https://crates.io",
Expand Down
Loading