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: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ A CLI tool and Go library for generating a Software Bill of Materials (SBOM) fro
### Supported Ecosystems

- Alpine (apk)
- C (conan)
- C++ (conan)
- Dart (pubs)
- Debian (dpkg)
- Dotnet (deps.json)
Expand Down
2 changes: 2 additions & 0 deletions internal/formats/common/spdxhelpers/source_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from rust cargo manifest"
case pkg.PhpComposerPkg:
answer = "acquired package info from PHP composer manifest"
case pkg.ConanPkg:
answer = "acquired package info from conan manifest"
default:
answer = "acquired package info from the following paths"
}
Expand Down
8 changes: 8 additions & 0 deletions internal/formats/common/spdxhelpers/source_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ func Test_SourceInfo(t *testing.T) {
"from ALPM DB",
},
},
{
input: pkg.Package{
Type: pkg.ConanPkg,
},
expected: []string{
"from conan manifest",
},
},
}
var pkgTypes []pkg.Type
for _, test := range tests {
Expand Down
8 changes: 7 additions & 1 deletion internal/formats/syftjson/model/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (p *Package) UnmarshalJSON(b []byte) error {
return unpackMetadata(p, unpacker)
}

// nolint:funlen
// nolint:funlen,gocognit,gocyclo
func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
p.MetadataType = unpacker.MetadataType
switch p.MetadataType {
Expand Down Expand Up @@ -145,6 +145,12 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error {
return err
}
p.Metadata = payload
case pkg.ConanaMetadataType:
var payload pkg.ConanMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
return err
}
p.Metadata = payload
case pkg.DotnetDepsMetadataType:
var payload pkg.DotnetDepsMetadata
if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions syft/pkg/cataloger/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/alpm"
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
"github.com/anchore/syft/syft/pkg/cataloger/dart"
"github.com/anchore/syft/syft/pkg/cataloger/deb"
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
Expand Down Expand Up @@ -75,6 +76,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
cpp.NewConanfileCataloger(),
}, cfg.Catalogers)
}

Expand All @@ -100,6 +102,7 @@ func AllCatalogers(cfg Config) []Cataloger {
dotnet.NewDotnetDepsCataloger(),
php.NewPHPComposerInstalledCataloger(),
php.NewPHPComposerLockCataloger(),
cpp.NewConanfileCataloger(),
}, cfg.Catalogers)
}

Expand Down
14 changes: 14 additions & 0 deletions syft/pkg/cataloger/cpp/cataloger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cpp

import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
)

// NewConanfileCataloger returns a new C++ Conanfile cataloger object.
func NewConanfileCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/conanfile.txt": parseConanfile,
}

return common.NewGenericCataloger(nil, globParsers, "conan-cataloger")
}
60 changes: 60 additions & 0 deletions syft/pkg/cataloger/cpp/parse_conanfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cpp

import (
"bufio"
"errors"
"fmt"
"io"
"strings"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
)

// integrity check
var _ common.ParserFn = parseConanfile

type Conanfile struct {
Requires []string `toml:"requires"`
}

// parseConanfile is a parser function for conanfile.txt contents, returning all packages discovered.
func parseConanfile(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
r := bufio.NewReader(reader)
inRequirements := false
pkgs := []*pkg.Package{}
for {
line, err := r.ReadString('\n')
switch {
case errors.Is(io.EOF, err):
return pkgs, nil, nil
case err != nil:
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
}

switch {
case strings.Contains(line, "[requires]"):
inRequirements = true
case strings.ContainsAny(line, "[]#"):
inRequirements = false
}

splits := strings.Split(strings.TrimSpace(line), "/")
if len(splits) < 2 || !inRequirements {
continue
}
pkgName, pkgVersion := splits[0], splits[1]
pkgs = append(pkgs, &pkg.Package{
Name: pkgName,
Version: pkgVersion,
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: pkgName,
Version: pkgVersion,
},
})
}
}
96 changes: 96 additions & 0 deletions syft/pkg/cataloger/cpp/parse_conanfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cpp

import (
"os"
"testing"

"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
)

func TestParseConanfile(t *testing.T) {
expected := []*pkg.Package{
{
Name: "catch2",
Version: "2.13.8",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "catch2",
Version: "2.13.8",
},
},
{
Name: "docopt.cpp",
Version: "0.6.3",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "docopt.cpp",
Version: "0.6.3",
},
},
{
Name: "fmt",
Version: "8.1.1",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "fmt",
Version: "8.1.1",
},
},
{
Name: "spdlog",
Version: "1.9.2",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "spdlog",
Version: "1.9.2",
},
},
{
Name: "sdl",
Version: "2.0.20",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "sdl",
Version: "2.0.20",
},
},
{
Name: "fltk",
Version: "1.3.8",
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanaMetadataType,
Metadata: pkg.ConanMetadata{
Name: "fltk",
Version: "1.3.8",
},
},
}

fixture, err := os.Open("test-fixtures/conanfile.txt")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}

// TODO: no relationships are under test yet
actual, _, err := parseConanfile(fixture.Name(), fixture)
if err != nil {
t.Error(err)
}

differences := deep.Equal(expected, actual)
if differences != nil {
t.Errorf("returned package list differed from expectation: %+v", differences)
}
}
12 changes: 12 additions & 0 deletions syft/pkg/cataloger/cpp/test-fixtures/conanfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Docs at https://docs.conan.io/en/latest/reference/conanfile_txt.html

[requires]
catch2/2.13.8
docopt.cpp/0.6.3
fmt/8.1.1
spdlog/1.9.2
sdl/2.0.20
fltk/1.3.8

[generators]
cmake_find_package_multi
24 changes: 24 additions & 0 deletions syft/pkg/conan_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package pkg

import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/linux"
)

type ConanMetadata struct {
Name string `mapstructure:"name" json:"name"`
Version string `mapstructure:"version" json:"version"`
}

func (m ConanMetadata) PackageURL(_ *linux.Release) string {
var qualifiers packageurl.Qualifiers

return packageurl.NewPackageURL(
packageurl.TypeConan,
"",
m.Name,
m.Version,
qualifiers,
"",
).ToString()
}
4 changes: 4 additions & 0 deletions syft/pkg/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
Rust Language = "rust"
Dart Language = "dart"
Dotnet Language = "dotnet"
CPP Language = "c++"
)

// AllLanguages is a set of all programming languages detected by syft.
Expand All @@ -34,6 +35,7 @@ var AllLanguages = []Language{
Rust,
Dart,
Dotnet,
CPP,
}

// String returns the string representation of the language.
Expand Down Expand Up @@ -70,6 +72,8 @@ func LanguageByName(name string) Language {
return Dart
case packageurl.TypeDotnet:
return Dotnet
case packageurl.TypeConan, string(CPP):
return CPP
default:
return UnknownLanguage
}
Expand Down
12 changes: 12 additions & 0 deletions syft/pkg/language_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func TestLanguageFromPURL(t *testing.T) {
purl: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?type=zip&classifier=dist",
want: Java,
},
{
purl: "pkg:conan/catch2@2.13.8",
want: CPP,
},
}

var languages []string
Expand Down Expand Up @@ -183,6 +187,14 @@ func TestLanguageByName(t *testing.T) {
name: "unknown",
language: UnknownLanguage,
},
{
name: "conan",
language: CPP,
},
{
name: "c++",
language: CPP,
},
}

for _, test := range tests {
Expand Down
3 changes: 3 additions & 0 deletions syft/pkg/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
KbPackageMetadataType MetadataType = "KbPackageMetadata"
GolangBinMetadataType MetadataType = "GolangBinMetadata"
PhpComposerJSONMetadataType MetadataType = "PhpComposerJsonMetadata"
ConanaMetadataType MetadataType = "ConanaMetadataType"
)

var AllMetadataTypes = []MetadataType{
Expand All @@ -42,6 +43,7 @@ var AllMetadataTypes = []MetadataType{
KbPackageMetadataType,
GolangBinMetadataType,
PhpComposerJSONMetadataType,
ConanaMetadataType,
}

var MetadataTypeByName = map[MetadataType]reflect.Type{
Expand All @@ -59,4 +61,5 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{
KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}),
GolangBinMetadataType: reflect.TypeOf(GolangBinMetadata{}),
PhpComposerJSONMetadataType: reflect.TypeOf(PhpComposerJSONMetadata{}),
ConanaMetadataType: reflect.TypeOf(ConanMetadata{}),
}
6 changes: 6 additions & 0 deletions syft/pkg/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
KbPkg Type = "msrc-kb"
DartPubPkg Type = "dart-pub"
DotnetPkg Type = "dotnet"
ConanPkg Type = "conan"
)

// AllPkgs represents all supported package types
Expand All @@ -42,6 +43,7 @@ var AllPkgs = []Type{
KbPkg,
DartPubPkg,
DotnetPkg,
ConanPkg,
}

// PackageURLType returns the PURL package type for the current package.
Expand Down Expand Up @@ -73,6 +75,8 @@ func (t Type) PackageURLType() string {
return packageurl.TypePub
case DotnetPkg:
return packageurl.TypeDotnet
case ConanPkg:
return packageurl.TypeConan
default:
// TODO: should this be a "generic" purl type instead?
return ""
Expand Down Expand Up @@ -116,6 +120,8 @@ func TypeByName(name string) Type {
return DartPubPkg
case packageurl.TypeDotnet:
return DotnetPkg
case packageurl.TypeConan:
return ConanPkg
default:
return UnknownPkg
}
Expand Down
Loading