feat: add support for conan packages (C/C++) (#1083)

This commit is contained in:
cpendery
2022-07-05 10:49:24 -04:00
committed by GitHub
parent 6b28a46ebe
commit 57323a1666
19 changed files with 299 additions and 2 deletions
+3
View File
@@ -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"
@@ -75,6 +76,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
cpp.NewConanfileCataloger(),
}, cfg.Catalogers)
}
@@ -100,6 +102,7 @@ func AllCatalogers(cfg Config) []Cataloger {
dotnet.NewDotnetDepsCataloger(),
php.NewPHPComposerInstalledCataloger(),
php.NewPHPComposerLockCataloger(),
cpp.NewConanfileCataloger(),
}, cfg.Catalogers)
}
+14
View File
@@ -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
View File
@@ -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,
},
})
}
}
@@ -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)
}
}
@@ -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
View File
@@ -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
View File
@@ -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.
@@ -34,6 +35,7 @@ var AllLanguages = []Language{
Rust,
Dart,
Dotnet,
CPP,
}
// String returns the string representation of the language.
@@ -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
}
+12
View File
@@ -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
@@ -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 {
+3
View File
@@ -25,6 +25,7 @@ const (
KbPackageMetadataType MetadataType = "KbPackageMetadata"
GolangBinMetadataType MetadataType = "GolangBinMetadata"
PhpComposerJSONMetadataType MetadataType = "PhpComposerJsonMetadata"
ConanaMetadataType MetadataType = "ConanaMetadataType"
)
var AllMetadataTypes = []MetadataType{
@@ -42,6 +43,7 @@ var AllMetadataTypes = []MetadataType{
KbPackageMetadataType,
GolangBinMetadataType,
PhpComposerJSONMetadataType,
ConanaMetadataType,
}
var MetadataTypeByName = map[MetadataType]reflect.Type{
@@ -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
View File
@@ -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
@@ -42,6 +43,7 @@ var AllPkgs = []Type{
KbPkg,
DartPubPkg,
DotnetPkg,
ConanPkg,
}
// PackageURLType returns the PURL package type for the current package.
@@ -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 ""
@@ -116,6 +120,8 @@ func TypeByName(name string) Type {
return DartPubPkg
case packageurl.TypeDotnet:
return DotnetPkg
case packageurl.TypeConan:
return ConanPkg
default:
return UnknownPkg
}
+4
View File
@@ -68,6 +68,10 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:alpm/arch/linux@5.10.0?arch=x86_64&distro=arch",
expected: AlpmPkg,
},
{
purl: "pkg:conan/catch2@2.13.8",
expected: ConanPkg,
},
}
var pkgTypes []string
+15
View File
@@ -208,6 +208,21 @@ func TestPackageURL(t *testing.T) {
expected: "pkg:alpm/arch/linux@5.10.0?distro=arch-rolling",
},
{
name: "conan",
pkg: Package{
Name: "catch2",
Version: "2.13.8",
Type: ConanPkg,
Language: CPP,
MetadataType: ConanaMetadataType,
Metadata: ConanMetadata{
Name: "catch2",
Version: "2.13.8",
},
},
expected: "pkg:conan/catch2@2.13.8",
},
}
var pkgTypes []string