mirror of
https://github.com/anchore/syft.git
synced 2026-07-04 18:18:26 +02:00
Vcpkg Cataloger (#4081)
* Vcpkg cataloger for vcpkg "Manifest Mode" Find and parse vcpkg-lock.json to get HEAD commit hash Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * just use local vcpkg git repo if it exists, clone it if it doesn't Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * Config opt for git remote clones for vcpkg and README update Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * Look in vcpkg cache git repo for custom git repos Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add triplet to metadata and support overlay-ports from config file Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * Add PURL to packages (not sure if this is correct) Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * flatten structs in pkg module and move vcpkg structs to resolver Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * account for overriden versions in toplevel manifest Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * generate json schema for vcpkg metadata Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * test for basic vcpkg project dependencies for vcpkg registry to be pulled in add tree hashes and use correct git hash in builtin-baseline for helloworld test vcpkg-registry for testing that uses object hashes from syft repo fix broken tests Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * formatting Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * fix static-analysis violations Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix integration test failure Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * remove uneeded files from vcpkg test fixture and use custom registry Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * change vcpkg registry to anchore one Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * purl spec based on open PR Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * generate-json-schema Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rebased and generate json schema 16.0.40 Signed-off-by: Gabriel Rau <gabetrau@gmail.com> * address low hanging fruit Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * handle additional comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * migrate to testdata Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * improve docs and testing Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix static analysis Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove license from pkg metadata Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix capabilities claim Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Gabriel Rau <gabetrau@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
148fe572bc
commit
656a4d46d7
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||||
@ -44,6 +45,7 @@ type Catalog struct {
|
|||||||
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
|
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
|
||||||
|
|
||||||
// ecosystem-specific cataloger configuration
|
// ecosystem-specific cataloger configuration
|
||||||
|
Cpp cppConfig `yaml:"cpp" json:"cpp" mapstructure:"cpp"`
|
||||||
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
Dotnet dotnetConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
||||||
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||||
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
|
Java javaConfig `yaml:"java" json:"java" mapstructure:"java"`
|
||||||
@ -80,6 +82,7 @@ func DefaultCatalog() Catalog {
|
|||||||
JavaScript: defaultJavaScriptConfig(),
|
JavaScript: defaultJavaScriptConfig(),
|
||||||
Python: defaultPythonConfig(),
|
Python: defaultPythonConfig(),
|
||||||
Nix: defaultNixConfig(),
|
Nix: defaultNixConfig(),
|
||||||
|
Cpp: defaultCppConfig(),
|
||||||
Dotnet: defaultDotnetConfig(),
|
Dotnet: defaultDotnetConfig(),
|
||||||
Golang: defaultGolangConfig(),
|
Golang: defaultGolangConfig(),
|
||||||
Java: defaultJavaConfig(),
|
Java: defaultJavaConfig(),
|
||||||
@ -172,6 +175,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
|||||||
}
|
}
|
||||||
return pkgcataloging.Config{
|
return pkgcataloging.Config{
|
||||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||||
|
Cpp: cpp.DefaultCatalogerConfig().
|
||||||
|
WithVcpkgAllowGitClone(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Cpp, task.Vcpkg), cfg.Cpp.VcpkgAllowGitClone)),
|
||||||
Dotnet: dotnet.DefaultCatalogerConfig().
|
Dotnet: dotnet.DefaultCatalogerConfig().
|
||||||
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
|
||||||
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
|
||||||
@ -301,6 +306,7 @@ var publicisedEnrichmentOptions = []string{
|
|||||||
task.Java,
|
task.Java,
|
||||||
task.JavaScript,
|
task.JavaScript,
|
||||||
task.Python,
|
task.Python,
|
||||||
|
task.Vcpkg,
|
||||||
}
|
}
|
||||||
|
|
||||||
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
|
func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
|
||||||
|
|||||||
30
cmd/syft/internal/options/cpp.go
Normal file
30
cmd/syft/internal/options/cpp.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cppConfig struct {
|
||||||
|
VcpkgAllowGitClone *bool `yaml:"vcpkg-allow-git-clone" json:"vcpkg-allow-git-clone" mapstructure:"vcpkg-allow-git-clone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCppConfig() cppConfig {
|
||||||
|
// reference the cataloger default so capability generation can associate this config with the cpp
|
||||||
|
// ecosystem (it discovers ecosystem configs by their cataloger import). the value itself stays nil:
|
||||||
|
// nil defaults to false (no network), and leaving it unset lets --enrich opt in. cloning requires a
|
||||||
|
// network connection, which must be opt-in.
|
||||||
|
_ = cpp.DefaultCatalogerConfig()
|
||||||
|
return cppConfig{
|
||||||
|
VcpkgAllowGitClone: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ interface {
|
||||||
|
clio.FieldDescriber
|
||||||
|
} = (*cppConfig)(nil)
|
||||||
|
|
||||||
|
func (o *cppConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||||
|
descriptions.Add(&o.VcpkgAllowGitClone, `enables Syft to use clone remote repositories for vcpkg custom git registries.
|
||||||
|
(also useful if the builtin vcpkg registry is not cloned locally)`)
|
||||||
|
}
|
||||||
@ -89,6 +89,7 @@ func TestPkgCoverageImage(t *testing.T) {
|
|||||||
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead
|
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // we have coverage for pear instead
|
||||||
definedPkgs.Remove(string(pkg.CondaPkg))
|
definedPkgs.Remove(string(pkg.CondaPkg))
|
||||||
definedPkgs.Remove(string(pkg.ModelPkg))
|
definedPkgs.Remove(string(pkg.ModelPkg))
|
||||||
|
definedPkgs.Remove(string(pkg.VcpkgPkg))
|
||||||
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
||||||
|
|
||||||
var cases []testCase
|
var cases []testCase
|
||||||
@ -165,6 +166,7 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
|||||||
definedPkgs.Remove(string(pkg.CondaPkg))
|
definedPkgs.Remove(string(pkg.CondaPkg))
|
||||||
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages
|
definedPkgs.Remove(string(pkg.PhpPeclPkg)) // this is covered as pear packages
|
||||||
definedPkgs.Remove(string(pkg.ModelPkg))
|
definedPkgs.Remove(string(pkg.ModelPkg))
|
||||||
|
definedPkgs.Remove(string(pkg.VcpkgPkg))
|
||||||
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
definedPkgs.Remove(string(pkg.AppleAppBundlePkg))
|
||||||
|
|
||||||
// for directory scans we should not expect to see any of the following package types
|
// for directory scans we should not expect to see any of the following package types
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
# This file is partially auto-generated. Run 'go generate ./internal/capabilities' to regenerate.
|
# This file is partially auto-generated. Run 'go generate ./internal/capabilities' to regenerate.
|
||||||
|
|
||||||
application: # AUTO-GENERATED - application-level config keys
|
application: # AUTO-GENERATED - application-level config keys
|
||||||
|
- key: cpp.vcpkg-allow-git-clone
|
||||||
|
description: enables Syft to use clone remote repositories for vcpkg custom git registries. (also useful if the builtin vcpkg registry is not cloned locally)
|
||||||
- key: dotnet.dep-packages-must-claim-dll
|
- key: dotnet.dep-packages-must-claim-dll
|
||||||
description: only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk). The package is also included if any child package claims a DLL, even if the package itself does not claim a DLL.
|
description: only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk). The package is also included if any child package claims a DLL, even if the package itself does not claim a DLL.
|
||||||
- key: dotnet.dep-packages-must-have-dll
|
- key: dotnet.dep-packages-must-have-dll
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package internal
|
|||||||
const (
|
const (
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "16.1.7"
|
JSONSchemaVersion = "16.1.8"
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||||
@ -14,5 +14,5 @@ const (
|
|||||||
// 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support
|
// 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support
|
||||||
// 16.1.6 - add Dependencies to ElixirMixLockEntry metadata
|
// 16.1.6 - add Dependencies to ElixirMixLockEntry metadata
|
||||||
// 16.1.7 - add AppleAppBundleEntry metadata type for the apple app bundle cataloger
|
// 16.1.7 - add AppleAppBundleEntry metadata type for the apple app bundle cataloger
|
||||||
|
// 16.1.8 - add VcpkgManifest metadata type for vcpkg manifest support
|
||||||
)
|
)
|
||||||
|
|||||||
@ -72,6 +72,7 @@ func AllTypes() []any {
|
|||||||
pkg.SwiftPackageManagerResolvedEntry{},
|
pkg.SwiftPackageManagerResolvedEntry{},
|
||||||
pkg.SwiplPackEntry{},
|
pkg.SwiplPackEntry{},
|
||||||
pkg.TerraformLockProviderEntry{},
|
pkg.TerraformLockProviderEntry{},
|
||||||
|
pkg.VcpkgManifest{},
|
||||||
pkg.WordpressPluginEntry{},
|
pkg.WordpressPluginEntry{},
|
||||||
pkg.YarnLockEntry{},
|
pkg.YarnLockEntry{},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,6 +130,7 @@ var jsonTypes = makeJSONTypes(
|
|||||||
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
|
jsonNames(pkg.DotnetPackagesLockEntry{}, "dotnet-packages-lock-entry"),
|
||||||
jsonNames(pkg.CondaMetaPackage{}, "conda-metadata-entry", "CondaPackageMetadata"),
|
jsonNames(pkg.CondaMetaPackage{}, "conda-metadata-entry", "CondaPackageMetadata"),
|
||||||
jsonNames(pkg.GGUFFileHeader{}, "gguf-file-header"),
|
jsonNames(pkg.GGUFFileHeader{}, "gguf-file-header"),
|
||||||
|
jsonNames(pkg.VcpkgManifest{}, "vcpkg-manifest"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func expandLegacyNameVariants(names ...string) []string {
|
func expandLegacyNameVariants(names ...string) []string {
|
||||||
|
|||||||
@ -57,6 +57,10 @@ const (
|
|||||||
|
|
||||||
// Python ecosystem labels
|
// Python ecosystem labels
|
||||||
Python = "python"
|
Python = "python"
|
||||||
|
|
||||||
|
// C/C++ ecosystem labels
|
||||||
|
Cpp = "cpp"
|
||||||
|
Vcpkg = "vcpkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
@ -83,6 +87,11 @@ func DefaultPackageTaskFactories() Factories {
|
|||||||
|
|
||||||
// language-specific package declared catalogers ///////////////////////////////////////////////////////////////////////////
|
// language-specific package declared catalogers ///////////////////////////////////////////////////////////////////////////
|
||||||
newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"),
|
newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"),
|
||||||
|
newPackageTaskFactory(
|
||||||
|
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||||
|
return cpp.NewVcpkgManifestCataloger(cfg.PackagesConfig.Cpp)
|
||||||
|
},
|
||||||
|
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Cpp, Vcpkg),
|
||||||
newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
|
newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
|
||||||
newSimplePackageTaskFactory(dart.NewPubspecCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
|
newSimplePackageTaskFactory(dart.NewPubspecCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
|
||||||
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
|
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
|
||||||
|
|||||||
4505
schema/json/schema-16.1.8.json
Normal file
4505
schema/json/schema-16.1.8.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "anchore.io/schema/syft/json/16.1.7/document",
|
"$id": "anchore.io/schema/syft/json/16.1.8/document",
|
||||||
"$ref": "#/$defs/Document",
|
"$ref": "#/$defs/Document",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"AlpmDbEntry": {
|
"AlpmDbEntry": {
|
||||||
@ -2927,6 +2927,9 @@
|
|||||||
{
|
{
|
||||||
"$ref": "#/$defs/TerraformLockProviderEntry"
|
"$ref": "#/$defs/TerraformLockProviderEntry"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/VcpkgManifest"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/$defs/WordpressPluginEntry"
|
"$ref": "#/$defs/WordpressPluginEntry"
|
||||||
}
|
}
|
||||||
@ -4385,6 +4388,86 @@
|
|||||||
],
|
],
|
||||||
"description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)."
|
"description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)."
|
||||||
},
|
},
|
||||||
|
"VcpkgManifest": {
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"documentation": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"full-version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"port-version": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"maintainers": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"supports": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"registry": {
|
||||||
|
"$ref": "#/$defs/VcpkgRegistryEntry",
|
||||||
|
"description": "to show where it came from"
|
||||||
|
},
|
||||||
|
"triplet": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "found by looking at build folder to find target. ex. \"x64-linux\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"full-version",
|
||||||
|
"version",
|
||||||
|
"port-version",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"description": "used for metadata."
|
||||||
|
},
|
||||||
|
"VcpkgRegistryEntry": {
|
||||||
|
"properties": {
|
||||||
|
"baseline": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"kind"
|
||||||
|
],
|
||||||
|
"description": "Matches definition of Vcpkg \"Registry\"."
|
||||||
|
},
|
||||||
"WordpressPluginEntry": {
|
"WordpressPluginEntry": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"pluginInstallDirectory": {
|
"pluginInstallDirectory": {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package pkgcataloging
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Binary binary.ClassifierCatalogerConfig `yaml:"binary" json:"binary" mapstructure:"binary"`
|
Binary binary.ClassifierCatalogerConfig `yaml:"binary" json:"binary" mapstructure:"binary"`
|
||||||
|
Cpp cpp.CatalogerConfig `yaml:"cpp" json:"cpp" mapstructure:"cpp"`
|
||||||
Dotnet dotnet.CatalogerConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
Dotnet dotnet.CatalogerConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
|
||||||
Golang golang.CatalogerConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
Golang golang.CatalogerConfig `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||||
JavaArchive java.ArchiveCatalogerConfig `yaml:"java-archive" json:"java-archive" mapstructure:"java-archive"`
|
JavaArchive java.ArchiveCatalogerConfig `yaml:"java-archive" json:"java-archive" mapstructure:"java-archive"`
|
||||||
@ -25,6 +27,7 @@ type Config struct {
|
|||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Binary: binary.DefaultClassifierCatalogerConfig(),
|
Binary: binary.DefaultClassifierCatalogerConfig(),
|
||||||
|
Cpp: cpp.DefaultCatalogerConfig(),
|
||||||
Dotnet: dotnet.DefaultCatalogerConfig(),
|
Dotnet: dotnet.DefaultCatalogerConfig(),
|
||||||
Golang: golang.DefaultCatalogerConfig(),
|
Golang: golang.DefaultCatalogerConfig(),
|
||||||
JavaArchive: java.DefaultArchiveCatalogerConfig(),
|
JavaArchive: java.DefaultArchiveCatalogerConfig(),
|
||||||
@ -44,6 +47,11 @@ func (c Config) WithBinaryConfig(cfg binary.ClassifierCatalogerConfig) Config {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Config) WithCppConfig(cfg cpp.CatalogerConfig) Config {
|
||||||
|
c.Cpp = cfg
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func (c Config) WithDotnetConfig(cfg dotnet.CatalogerConfig) Config {
|
func (c Config) WithDotnetConfig(cfg dotnet.CatalogerConfig) Config {
|
||||||
c.Dotnet = cfg
|
c.Dotnet = cfg
|
||||||
return c
|
return c
|
||||||
|
|||||||
@ -138,6 +138,11 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,f
|
|||||||
|
|
||||||
case pkg.SwiplPackEntry:
|
case pkg.SwiplPackEntry:
|
||||||
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
|
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
|
||||||
|
|
||||||
|
case pkg.VcpkgManifest:
|
||||||
|
if len(metadata.Maintainers) > 0 {
|
||||||
|
author = metadata.Maintainers[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if typ == "" && author != "" {
|
if typ == "" && author != "" {
|
||||||
|
|||||||
@ -60,6 +60,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
|||||||
pkg.GGUFFileHeader{},
|
pkg.GGUFFileHeader{},
|
||||||
pkg.DenoLockEntry{},
|
pkg.DenoLockEntry{},
|
||||||
pkg.DenoRemoteLockEntry{},
|
pkg.DenoRemoteLockEntry{},
|
||||||
|
pkg.VcpkgManifest{},
|
||||||
)
|
)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -86,6 +86,8 @@ func SourceInfo(p pkg.Package) string {
|
|||||||
answer = "acquired package info from Terraform dependency lock file"
|
answer = "acquired package info from Terraform dependency lock file"
|
||||||
case pkg.ModelPkg:
|
case pkg.ModelPkg:
|
||||||
answer = "acquired package info from AI artifact (e.g. GGUF File)"
|
answer = "acquired package info from AI artifact (e.g. GGUF File)"
|
||||||
|
case pkg.VcpkgPkg:
|
||||||
|
answer = "acquired package info from vcpkg manifest file"
|
||||||
default:
|
default:
|
||||||
answer = "acquired package info from the following paths"
|
answer = "acquired package info from the following paths"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,6 +199,14 @@ func Test_SourceInfo(t *testing.T) {
|
|||||||
"from conda metadata",
|
"from conda metadata",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: pkg.Package{
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
},
|
||||||
|
expected: []string{
|
||||||
|
"from vcpkg manifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: pkg.Package{
|
input: pkg.Package{
|
||||||
Type: pkg.PortagePkg,
|
Type: pkg.PortagePkg,
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
# Cataloger capabilities. See ../README.md for documentation.
|
# Cataloger capabilities. See ../README.md for documentation.
|
||||||
|
|
||||||
|
configs: # AUTO-GENERATED - config structs and their fields
|
||||||
|
cpp.CatalogerConfig:
|
||||||
|
fields:
|
||||||
|
- key: VcpkgAllowGitClone
|
||||||
|
description: VcpkgAllowGitClone enables cloning remote git registries to resolve vcpkg manifest dependencies (requires network access).
|
||||||
|
app_key: cpp.vcpkg-allow-git-clone
|
||||||
catalogers:
|
catalogers:
|
||||||
- ecosystem: c++ # MANUAL
|
- ecosystem: c++ # MANUAL
|
||||||
name: conan-cataloger # AUTO-GENERATED
|
name: conan-cataloger # AUTO-GENERATED
|
||||||
@ -126,3 +132,49 @@ catalogers:
|
|||||||
default: false
|
default: false
|
||||||
- name: package_manager.package_integrity_hash
|
- name: package_manager.package_integrity_hash
|
||||||
default: false
|
default: false
|
||||||
|
- ecosystem: c++ # MANUAL
|
||||||
|
name: vcpkg-manifest-cataloger # AUTO-GENERATED
|
||||||
|
type: generic # AUTO-GENERATED
|
||||||
|
source: # AUTO-GENERATED
|
||||||
|
file: syft/pkg/cataloger/cpp/cataloger.go
|
||||||
|
function: NewVcpkgManifestCataloger
|
||||||
|
config: cpp.CatalogerConfig # AUTO-GENERATED
|
||||||
|
selectors: # AUTO-GENERATED
|
||||||
|
- cpp
|
||||||
|
- declared
|
||||||
|
- directory
|
||||||
|
- language
|
||||||
|
- package
|
||||||
|
- vcpkg
|
||||||
|
parsers: # AUTO-GENERATED structure
|
||||||
|
- function: parseVcpkgManifest
|
||||||
|
detector: # AUTO-GENERATED
|
||||||
|
method: glob # AUTO-GENERATED
|
||||||
|
criteria: # AUTO-GENERATED
|
||||||
|
- '**/vcpkg.json'
|
||||||
|
metadata_types: # AUTO-GENERATED
|
||||||
|
- pkg.VcpkgManifest
|
||||||
|
package_types: # AUTO-GENERATED
|
||||||
|
- vcpkg
|
||||||
|
purl_types: # AUTO-GENERATED
|
||||||
|
- vcpkg
|
||||||
|
json_schema_types: # AUTO-GENERATED
|
||||||
|
- VcpkgManifest
|
||||||
|
capabilities: # MANUAL - preserved across regeneration
|
||||||
|
- name: license
|
||||||
|
default: true
|
||||||
|
- name: dependency.depth
|
||||||
|
default:
|
||||||
|
- direct
|
||||||
|
- indirect
|
||||||
|
- name: dependency.edges
|
||||||
|
default: complete
|
||||||
|
- name: dependency.kinds
|
||||||
|
default:
|
||||||
|
- runtime
|
||||||
|
- name: package_manager.files.listing
|
||||||
|
default: false
|
||||||
|
- name: package_manager.files.digests
|
||||||
|
default: false
|
||||||
|
- name: package_manager.package_integrity_hash
|
||||||
|
default: false
|
||||||
|
|||||||
@ -20,3 +20,35 @@ func NewConanInfoCataloger() pkg.Cataloger {
|
|||||||
return generic.NewCataloger("conan-info-cataloger").
|
return generic.NewCataloger("conan-info-cataloger").
|
||||||
WithParserByGlobs(parseConaninfo, "**/conaninfo.txt")
|
WithParserByGlobs(parseConaninfo, "**/conaninfo.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vcpkg (the Microsoft C/C++ package manager) has two modes
|
||||||
|
// (https://learn.microsoft.com/en-us/vcpkg/concepts/classic-mode):
|
||||||
|
// - classic mode: `vcpkg install <pkg>` populates a central tree at $VCPKG_ROOT/installed/.
|
||||||
|
// - manifest mode: a vcpkg.json declares dependencies; after a build the resolved tree appears
|
||||||
|
// under vcpkg_installed/ (a vcpkg/status DB + per-triplet dirs).
|
||||||
|
//
|
||||||
|
// what is on disk depends on where in the lifecycle the scan happens:
|
||||||
|
// - source checkout (the dir-scan case): vcpkg.json (+ vcpkg-configuration.json, overlay ports)
|
||||||
|
// are present. vcpkg_installed/ is a build artifact and is gitignored, so it is NOT present;
|
||||||
|
// exact transitive versions live in the registry, not the manifest.
|
||||||
|
// - built artifact: vcpkg_installed/ holds the actually-installed truth.
|
||||||
|
//
|
||||||
|
// NewVcpkgManifestCataloger covers ONLY the manifest (dir/source) case: it reads vcpkg.json and its
|
||||||
|
// declared dependencies and resolves each dependency's manifest from the vcpkg registry. resolving the
|
||||||
|
// registry needs a local registry clone or a network clone, which is opt-in via
|
||||||
|
// CatalogerConfig.VcpkgAllowGitClone (wired to --enrich).
|
||||||
|
//
|
||||||
|
// it deliberately does NOT cover installed state: vcpkg_installed/ (vcpkg/status, per-package
|
||||||
|
// vcpkg.spdx.json, copyright, ABI info), the build triplet (a build-time choice recorded only under
|
||||||
|
// vcpkg_installed/), and classic-mode central installs.
|
||||||
|
//
|
||||||
|
// why there is no installed-state (vcpkg/status) cataloger yet: vcpkg_installed/ tends to be a gitignored
|
||||||
|
// build artifact, so whether it survives into a scannable target is pattern-dependent. it is dropped by slim
|
||||||
|
// multi-stage runtime images (the documented best practice copies only the app binary / binary cache
|
||||||
|
// between stages) and by dev/base images (which bootstrap the tool, not packages); no canonical public
|
||||||
|
// image ships a populated vcpkg/status by default. it is retained mainly in single-stage builder / CI /
|
||||||
|
// "fat" app images. so a status-based cataloger has real but not universal payoff and is deferred until
|
||||||
|
// it is worth the maintenance.
|
||||||
|
func NewVcpkgManifestCataloger(opts CatalogerConfig) pkg.Cataloger {
|
||||||
|
return generic.NewCataloger("vcpkg-manifest-cataloger").WithParserByGlobs(newVcpkgCataloger(opts.VcpkgAllowGitClone).parseVcpkgManifest, "**/vcpkg.json")
|
||||||
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCataloger_Globs(t *testing.T) {
|
func TestCatalogerConan_Globs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
@ -32,7 +32,7 @@ func TestCataloger_Globs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCatalogerInfo_Globs(t *testing.T) {
|
func TestCatalogerConanInfo_Globs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
|
|||||||
19
syft/pkg/cataloger/cpp/config.go
Normal file
19
syft/pkg/cataloger/cpp/config.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package cpp
|
||||||
|
|
||||||
|
type CatalogerConfig struct {
|
||||||
|
// VcpkgAllowGitClone enables cloning remote git registries to resolve vcpkg manifest dependencies (requires network access).
|
||||||
|
// app-config: cpp.vcpkg-allow-git-clone
|
||||||
|
VcpkgAllowGitClone bool `yaml:"vcpkg-allow-git-clone" json:"vcpkg-allow-git-clone" mapstructure:"vcpkg-allow-git-clone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultCatalogerConfig() CatalogerConfig {
|
||||||
|
return CatalogerConfig{
|
||||||
|
// syft defaults to not sending requests over the network. You must opt in
|
||||||
|
VcpkgAllowGitClone: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CatalogerConfig) WithVcpkgAllowGitClone(input bool) CatalogerConfig {
|
||||||
|
c.VcpkgAllowGitClone = input
|
||||||
|
return c
|
||||||
|
}
|
||||||
697
syft/pkg/cataloger/cpp/internal/vcpkg/resolver.go
Normal file
697
syft/pkg/cataloger/cpp/internal/vcpkg/resolver.go
Normal file
@ -0,0 +1,697 @@
|
|||||||
|
package vcpkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
|
"github.com/go-git/go-git/v5/storage"
|
||||||
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/cache"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this is the default registry for vcpkg. it is the default "builtin" registry if a builtin one isn't specified
|
||||||
|
var defaultRegistry = pkg.VcpkgRegistryEntry{
|
||||||
|
Baseline: "master",
|
||||||
|
Kind: pkg.Git,
|
||||||
|
Repository: "https://github.com/microsoft/vcpkg",
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents contents of "vcpkg.json" file. (a.k.a the manifest file)
|
||||||
|
type Vcpkg struct {
|
||||||
|
BuiltinBaseline string `json:"builtin-baseline,omitempty"`
|
||||||
|
DefaultFeatures []any `json:"default-features,omitempty"`
|
||||||
|
// string or []VcpkgDependency
|
||||||
|
Dependencies []any `json:"dependencies,omitempty"`
|
||||||
|
// string or []string
|
||||||
|
Description any `json:"description,omitempty"`
|
||||||
|
Documentation string `json:"documentation,omitempty"`
|
||||||
|
Features map[string]vcpkgFeatureEntry `json:"features,omitempty"`
|
||||||
|
Homepage string `json:"homepage,omitempty"`
|
||||||
|
// In SPDX license expression format. see https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json
|
||||||
|
License string `json:"license,omitempty"`
|
||||||
|
Maintainers []string `json:"maintainers,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
// Only overrides defined by the top-level project are used
|
||||||
|
Overrides []vcpkgOverrideEntry `json:"overrides,omitempty"`
|
||||||
|
PortVersion float64 `json:"port-version,omitempty"`
|
||||||
|
Supports string `json:"supports,omitempty"`
|
||||||
|
// at most one of these version fields will be present and represent different versioning strategies
|
||||||
|
// see https://learn.microsoft.com/en-us/vcpkg/users/versioning#version-schemes for more details
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
VersionSemver string `json:"version-semver,omitempty"`
|
||||||
|
VersionDate string `json:"version-date,omitempty"`
|
||||||
|
VersionString string `json:"version-string,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confusingly not the same as Feature Object
|
||||||
|
// see https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json#feature vs https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json#feature-object
|
||||||
|
type vcpkgFeatureEntry struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
// string or []VcpkgDependency
|
||||||
|
Dependencies []any `json:"dependencies,omitempty"`
|
||||||
|
// "Platform expression"
|
||||||
|
Supports string `json:"supports,omitempty"`
|
||||||
|
// SPDX license expression
|
||||||
|
License string `json:"license,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vcpkgOverrideEntry struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
VersionSemver string `json:"version-semver,omitempty"`
|
||||||
|
VersionDate string `json:"version-date,omitempty"`
|
||||||
|
VersionString string `json:"version-string,omitempty"`
|
||||||
|
PortVersion float64 `json:"port-version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents contents of "vcpkg-configuration.json" file
|
||||||
|
type Config struct {
|
||||||
|
DefaultRegistry *pkg.VcpkgRegistryEntry `json:"default-registry"`
|
||||||
|
OverlayPorts []string `json:"overlay-ports,omitempty"`
|
||||||
|
OverlayTriplets []string `json:"overlay-triplets,omitempty"`
|
||||||
|
Registries []pkg.VcpkgRegistryEntry `json:"registries,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to get specific dependency from git history
|
||||||
|
type vcpkgGitVersionObjectEntry struct {
|
||||||
|
// Sha1 value used to retrieve specific git tree object from Github. https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28
|
||||||
|
GitTree string `json:"git-tree"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
VersionSemver string `json:"version-semver,omitempty"`
|
||||||
|
VersionDate string `json:"version-date,omitempty"`
|
||||||
|
VersionString string `json:"version-string,omitempty"`
|
||||||
|
PortVersion float64 `json:"port-version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents versions file "<name-of-dependency>.json" found in versions folder
|
||||||
|
type vcpkgGitVersions struct {
|
||||||
|
Versions []vcpkgGitVersionObjectEntry `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filesystem VersionObject
|
||||||
|
type vcpkgFsVersionObjectEntry struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
VersionSemver string `json:"version-semver,omitempty"`
|
||||||
|
VersionDate string `json:"version-date,omitempty"`
|
||||||
|
VersionString string `json:"version-string,omitempty"`
|
||||||
|
PortVersion float64 `json:"port-version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// represents filesystem versions file "<name-of-dependency>.json" found in versions folder
|
||||||
|
type vcpkgFsVersions struct {
|
||||||
|
Versions []vcpkgFsVersionObjectEntry `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpful to define relationships between Vcpkgs
|
||||||
|
// ResolvedManifest is a source manifest (vcpkg.json) together with the registry it was resolved from.
|
||||||
|
// it carries the raw Vcpkg so the cataloger can build the SBOM metadata and set package-level fields
|
||||||
|
// (e.g. license) on pkg.Package rather than on the metadata.
|
||||||
|
type ResolvedManifest struct {
|
||||||
|
Vcpkg *Vcpkg
|
||||||
|
Registry *pkg.VcpkgRegistryEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManifestNode struct {
|
||||||
|
Parent *ResolvedManifest
|
||||||
|
Child *ResolvedManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
type vcpkgBaselineVersionObjectEntry struct {
|
||||||
|
Baseline string `json:"baseline"`
|
||||||
|
PortVersion float64 `json:"port-version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vcpkg) GetFullVersion() string {
|
||||||
|
popVer := getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
if v.PortVersion != 0 {
|
||||||
|
return popVer + "#" + strconv.Itoa(int(v.PortVersion))
|
||||||
|
}
|
||||||
|
return popVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vcpkgGitVersionObjectEntry) GetFullVersion() string {
|
||||||
|
popVer := getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
if v.PortVersion != 0 {
|
||||||
|
return popVer + "#" + strconv.Itoa(int(v.PortVersion))
|
||||||
|
}
|
||||||
|
return popVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vcpkgFsVersionObjectEntry) GetFullVersion() string {
|
||||||
|
popVer := getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
if v.PortVersion != 0 {
|
||||||
|
return popVer + "#" + strconv.Itoa(int(v.PortVersion))
|
||||||
|
}
|
||||||
|
return popVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vcpkgOverrideEntry) GetFullVersion() string {
|
||||||
|
popVer := getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
if v.PortVersion != 0 {
|
||||||
|
return popVer + "#" + strconv.Itoa(int(v.PortVersion))
|
||||||
|
}
|
||||||
|
return popVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vcpkgGitVersionObjectEntry) GetPopulatedVersion() string {
|
||||||
|
return getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *vcpkgFsVersionObjectEntry) GetPopulatedVersion() string {
|
||||||
|
return getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vcpkg) GetPopulatedVersion() string {
|
||||||
|
return getPopulatedVersion(v.Version, v.VersionSemver, v.VersionDate, v.VersionString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPopulatedVersion(version, versionSemver, versionDate, versionString string) string {
|
||||||
|
switch {
|
||||||
|
case version != "":
|
||||||
|
return version
|
||||||
|
case versionSemver != "":
|
||||||
|
return versionSemver
|
||||||
|
case versionDate != "":
|
||||||
|
return versionDate
|
||||||
|
case versionString != "":
|
||||||
|
return versionString
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vcpkgRepo string = "https://github.com/microsoft/vcpkg"
|
||||||
|
|
||||||
|
type ID struct {
|
||||||
|
location string
|
||||||
|
head string
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolver is a short-lived utility to resolve vcpkg manifests from multiple sources, including:
|
||||||
|
// the filesystem, local maven cache directories, remote maven repositories, and the syft cache
|
||||||
|
type Resolver struct {
|
||||||
|
ctx context.Context
|
||||||
|
gitRepos map[string]storage.Storer
|
||||||
|
allowGitClone bool
|
||||||
|
cfg *Config
|
||||||
|
resolved map[ID]*ResolvedManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResolver constructs a new Resolver with the given vcpkg configuration.
|
||||||
|
func NewResolver(ctx context.Context, cfg *Config, allowGitClone bool) *Resolver {
|
||||||
|
return &Resolver{
|
||||||
|
ctx: ctx,
|
||||||
|
gitRepos: map[string]storage.Storer{},
|
||||||
|
allowGitClone: allowGitClone,
|
||||||
|
cfg: cfg,
|
||||||
|
resolved: map[ID]*ResolvedManifest{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all of the manifest/vcpkg.json files for a vcpkg dependency
|
||||||
|
func (r *Resolver) FindManifests(dependency any, df bool, builtinBaseline string, overrides []vcpkgOverrideEntry, parent *ResolvedManifest) ([]ManifestNode, error) {
|
||||||
|
var name string
|
||||||
|
var fullVersion string
|
||||||
|
defaultFeatures := df
|
||||||
|
var features []any
|
||||||
|
// can be either string or VcpkgDependency
|
||||||
|
switch d := dependency.(type) {
|
||||||
|
case string:
|
||||||
|
name = d
|
||||||
|
case map[string]any:
|
||||||
|
if n, ok := d["name"].(string); ok {
|
||||||
|
name = n
|
||||||
|
}
|
||||||
|
if v, ok := d["version>="].(string); ok {
|
||||||
|
fullVersion = v
|
||||||
|
}
|
||||||
|
if v, ok := d["default-features"].(bool); ok {
|
||||||
|
defaultFeatures = defaultFeatures && v
|
||||||
|
}
|
||||||
|
if v, ok := d["features"].([]any); ok {
|
||||||
|
features = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for when top-level manifest has this dependency version overridden
|
||||||
|
if over, ok := depVerOverriden(name, overrides); ok {
|
||||||
|
fullVersion = over.Version
|
||||||
|
}
|
||||||
|
reg := r.depRegistry(name, builtinBaseline)
|
||||||
|
vcpkg, err := r.findManifestFromReg(reg, name, fullVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
child := &ResolvedManifest{Vcpkg: vcpkg, Registry: reg}
|
||||||
|
// need to add dependency to map, even if it doesn't find its manifest, to avoid infinite loops caused by circular depenencies
|
||||||
|
switch reg.Kind {
|
||||||
|
case pkg.Git:
|
||||||
|
r.resolved[ID{reg.Repository, reg.Baseline, name, fullVersion}] = child
|
||||||
|
case pkg.Builtin:
|
||||||
|
vcpkgRoot := os.Getenv("VCPKG_ROOT")
|
||||||
|
r.resolved[ID{vcpkgRoot, reg.Baseline, name, fullVersion}] = child
|
||||||
|
case pkg.FileSystem:
|
||||||
|
r.resolved[ID{reg.Path, reg.Baseline, name, fullVersion}] = child
|
||||||
|
}
|
||||||
|
vcpkg.appendFtrDepsToDeps(defaultFeatures, features)
|
||||||
|
|
||||||
|
manNodes := []ManifestNode{}
|
||||||
|
manNodes = append(manNodes, ManifestNode{
|
||||||
|
Parent: parent,
|
||||||
|
Child: child,
|
||||||
|
})
|
||||||
|
if len(vcpkg.Dependencies) != 0 {
|
||||||
|
for _, dep := range vcpkg.Dependencies {
|
||||||
|
resolvedManifest, ok := r.depResolved(dep, builtinBaseline)
|
||||||
|
if ok {
|
||||||
|
// this is to catch duplicates
|
||||||
|
manNodes = append(manNodes, ManifestNode{
|
||||||
|
// child is parent in this case
|
||||||
|
Parent: child,
|
||||||
|
Child: resolvedManifest,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
childManNodes, err := r.FindManifests(dep, df, builtinBaseline, overrides, child)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not find vcpkg.json file for dependency. %w", err)
|
||||||
|
}
|
||||||
|
manNodes = append(manNodes, childManNodes...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return manNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vcpkg) appendFtrDepsToDeps(df bool, features []any) {
|
||||||
|
// collect explicitly requested feature names (a feature is either a string or a feature object)
|
||||||
|
requested := map[string]bool{}
|
||||||
|
for _, feature := range features {
|
||||||
|
switch fo := feature.(type) {
|
||||||
|
case string:
|
||||||
|
requested[fo] = true
|
||||||
|
case map[string]any:
|
||||||
|
// json decodes a feature object into map[string]any, not a struct
|
||||||
|
if name, ok := fo["name"].(string); ok {
|
||||||
|
requested[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add each feature's deps at most once: when requested, or when it's a default feature and defaults are enabled.
|
||||||
|
// this must be independent of the requested loop so default features apply even when none are requested.
|
||||||
|
for name, f := range v.Features {
|
||||||
|
if requested[name] || (df && isDefaultFeature(name, v.DefaultFeatures)) {
|
||||||
|
v.Dependencies = append(v.Dependencies, f.Dependencies...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func depVerOverriden(name string, overrides []vcpkgOverrideEntry) (*vcpkgOverrideEntry, bool) {
|
||||||
|
for _, over := range overrides {
|
||||||
|
if over.Name == name {
|
||||||
|
return &over, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) resolveRepo(repoStr string) (*git.Repository, error) {
|
||||||
|
if r.gitRepos[repoStr] != nil {
|
||||||
|
return git.Open(r.gitRepos[repoStr], nil)
|
||||||
|
} else if r.allowGitClone {
|
||||||
|
repo, err := git.CloneContext(r.ctx, memory.NewStorage(), nil, &git.CloneOptions{
|
||||||
|
URL: repoStr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.gitRepos[repoStr] = repo.Storer
|
||||||
|
return repo, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not resolve %s. enable vcpkg-allow-git-clone flag to allow cloning of remote git repos", repoStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) getRepo(repoStr string, path *string) (*git.Repository, error) {
|
||||||
|
vcpkgCachePath := getVcpkgGitCachePath()
|
||||||
|
// best to use vcpkg cache. Only able to locate if it's in the same directory as syft cache
|
||||||
|
if r.gitRepos[repoStr] == nil && vcpkgCachePath != "" {
|
||||||
|
repo, err := git.PlainOpen(vcpkgCachePath)
|
||||||
|
if err == nil {
|
||||||
|
r.gitRepos[repoStr] = repo.Storer
|
||||||
|
return repo, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.gitRepos[repoStr] == nil && path != nil {
|
||||||
|
repo, err := git.PlainOpen(*path)
|
||||||
|
if err == nil {
|
||||||
|
r.gitRepos[repoStr] = repo.Storer
|
||||||
|
return repo, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.resolveRepo(repoStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get path of vcpkg cache
|
||||||
|
func getVcpkgGitCachePath() string {
|
||||||
|
roots := cache.GetManager().RootDirs()
|
||||||
|
if len(roots) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// vcpkg keeps its git registry cache as a sibling of the syft cache dir
|
||||||
|
return roots[0] + "/../vcpkg/registries/git"
|
||||||
|
}
|
||||||
|
|
||||||
|
// determines which registry to use by the name of the dependency
|
||||||
|
func (r *Resolver) depRegistry(name string, builtinBaseline string) *pkg.VcpkgRegistryEntry {
|
||||||
|
var reg *pkg.VcpkgRegistryEntry
|
||||||
|
if r.cfg != nil {
|
||||||
|
for _, res := range r.cfg.Registries {
|
||||||
|
if slices.Contains(res.Packages, name) {
|
||||||
|
reg = &res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.cfg.DefaultRegistry != nil && reg == nil {
|
||||||
|
reg = r.cfg.DefaultRegistry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reg == nil {
|
||||||
|
// copy the package-global so concurrent catalogers don't clobber its baseline
|
||||||
|
def := defaultRegistry
|
||||||
|
def.Baseline = builtinBaseline
|
||||||
|
reg = &def
|
||||||
|
} else if reg.Kind == pkg.Builtin {
|
||||||
|
reg.Baseline = builtinBaseline
|
||||||
|
}
|
||||||
|
return reg
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if dependency has been retrieved already this run. Without this check, there were infinite loops from circular dependencies
|
||||||
|
func (r *Resolver) depResolved(dep any, builtinBaseline string) (*ResolvedManifest, bool) {
|
||||||
|
var name string
|
||||||
|
var version string
|
||||||
|
switch d := dep.(type) {
|
||||||
|
case string:
|
||||||
|
name = d
|
||||||
|
case map[string]any:
|
||||||
|
if n, ok := d["name"].(string); ok {
|
||||||
|
name = n
|
||||||
|
}
|
||||||
|
if v, ok := d["version>="].(string); ok {
|
||||||
|
version = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reg := r.depRegistry(name, builtinBaseline)
|
||||||
|
var location string
|
||||||
|
switch {
|
||||||
|
case reg.Repository != "":
|
||||||
|
location = reg.Repository
|
||||||
|
case reg.Path != "":
|
||||||
|
location = reg.Path
|
||||||
|
default:
|
||||||
|
location = vcpkgRepo
|
||||||
|
}
|
||||||
|
resolved, ok := r.resolved[ID{location, reg.Baseline, name, version}]
|
||||||
|
return resolved, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDefaultFeature(name string, defaultFeatures []any) bool {
|
||||||
|
for _, df := range defaultFeatures {
|
||||||
|
switch d := df.(type) {
|
||||||
|
case string:
|
||||||
|
if name == d {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case map[string]any:
|
||||||
|
if n, ok := d["name"].(string); ok && name == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) findManifestFromReg(reg *pkg.VcpkgRegistryEntry, name, fullVersion string) (*Vcpkg, error) {
|
||||||
|
if reg == nil {
|
||||||
|
return nil, fmt.Errorf("no vcpkg registry found which is required")
|
||||||
|
}
|
||||||
|
switch reg.Kind {
|
||||||
|
case pkg.Git:
|
||||||
|
var vcpkg *Vcpkg
|
||||||
|
var err error
|
||||||
|
if reg.Repository == "" {
|
||||||
|
return nil, fmt.Errorf("no repo found for vcpkg git registry")
|
||||||
|
}
|
||||||
|
if strings.TrimSuffix(reg.Repository, ".git") == vcpkgRepo {
|
||||||
|
path := os.Getenv("VCPKG_ROOT")
|
||||||
|
gitRepo, err := r.getRepo(vcpkgRepo, &path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vcpkg, err = r.getManifestFromGitRepo(gitRepo, reg.Baseline, name, fullVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gitRepo, err := r.getRepo(reg.Repository, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vcpkg, err = r.getManifestFromGitRepo(gitRepo, reg.Baseline, name, fullVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vcpkg, err
|
||||||
|
case pkg.Builtin:
|
||||||
|
path := os.Getenv("VCPKG_ROOT")
|
||||||
|
gitRepo, err := r.getRepo(vcpkgRepo, &path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vcpkg, err := r.getManifestFromGitRepo(gitRepo, reg.Baseline, name, fullVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcpkg, err
|
||||||
|
case pkg.FileSystem:
|
||||||
|
if reg.Path == "" {
|
||||||
|
return nil, fmt.Errorf("no path found for vcpkg filesystem registry")
|
||||||
|
}
|
||||||
|
baseline := reg.Baseline
|
||||||
|
if baseline == "" {
|
||||||
|
baseline = "default"
|
||||||
|
}
|
||||||
|
vcpkg, err := r.getManifestFromFilesystem(reg.Path, baseline, name, fullVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return vcpkg, err
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("vcpkg registry has no kind which is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// locates and gets the manifest file via the go-git
|
||||||
|
func (r *Resolver) getManifestFromGitRepo(repo *git.Repository, head, name, fullVersion string) (*Vcpkg, error) {
|
||||||
|
headObj, err := repo.CommitObject(plumbing.NewHash(head))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tree, err := headObj.Tree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var resultMan Vcpkg
|
||||||
|
if fullVersion != "" {
|
||||||
|
verPath := "versions/" + name[0:1] + "-/" + name + ".json"
|
||||||
|
verFile, err := findFileInTree(verPath, tree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get versions file from vcpkg git tree. %w", err)
|
||||||
|
}
|
||||||
|
content, err := verFile.Contents()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get contents of versions file from vcpkg git tree. %w", err)
|
||||||
|
}
|
||||||
|
var versions vcpkgGitVersions
|
||||||
|
err = json.Unmarshal([]byte(content), &versions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal versions file from vcpkg git tree. %w", err)
|
||||||
|
}
|
||||||
|
// get tree object sha for the port version
|
||||||
|
var gitTreeHash string
|
||||||
|
for _, v := range versions.Versions {
|
||||||
|
if fullVersion == v.GetFullVersion() {
|
||||||
|
gitTreeHash = v.GitTree
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gitTreeHash == "" {
|
||||||
|
return nil, fmt.Errorf("version %q not found in versions file for port %q", fullVersion, name)
|
||||||
|
}
|
||||||
|
verTree, err := repo.TreeObject(plumbing.NewHash(gitTreeHash))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tree from hash %v. %w", gitTreeHash, err)
|
||||||
|
}
|
||||||
|
manFile, err := verTree.File("vcpkg.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vcpkg.json file from git tree. %w", err)
|
||||||
|
}
|
||||||
|
manContents, err := manFile.Contents()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get contents of vcpkg.json file from git tree. %w", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(manContents), &resultMan)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
portPath := "ports/" + name + "/vcpkg.json"
|
||||||
|
manFile, err := findFileInTree(portPath, tree)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vcpkg.json file from ports directory in git tree. %w", err)
|
||||||
|
}
|
||||||
|
manContents, err := manFile.Contents()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get contents of vcpkg.json file from ports directory in git tree. %w", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(manContents), &resultMan)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &resultMan, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFileInTree(path string, tree *object.Tree) (*object.File, error) {
|
||||||
|
verFile, err := tree.File(path)
|
||||||
|
return verFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// locates and gets the manifest file via the filesystem path
|
||||||
|
func (r *Resolver) getManifestFromFilesystem(path, baseline, name, fullVersion string) (*Vcpkg, error) {
|
||||||
|
if path == "" {
|
||||||
|
return &Vcpkg{}, fmt.Errorf("no/empty path specified for vcpkg filesystem registry")
|
||||||
|
}
|
||||||
|
var finalVer string
|
||||||
|
if fullVersion == "" {
|
||||||
|
baselineBytes, err := os.ReadFile(path + "/versions/baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
return &Vcpkg{}, err
|
||||||
|
}
|
||||||
|
var baselineGen map[string]any
|
||||||
|
err = json.Unmarshal(baselineBytes, &baselineGen)
|
||||||
|
if err != nil {
|
||||||
|
return &Vcpkg{}, err
|
||||||
|
}
|
||||||
|
var baselineMatch map[string]any
|
||||||
|
for k, v := range baselineGen {
|
||||||
|
if k == baseline {
|
||||||
|
baselineMatch, _ = v.(map[string]any)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var baselineVer vcpkgBaselineVersionObjectEntry
|
||||||
|
for k, v := range baselineMatch {
|
||||||
|
if k == name {
|
||||||
|
foundBaseline, ok := v.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bl, _ := foundBaseline["baseline"].(string)
|
||||||
|
// default for go json package when unmarshalling number is float64
|
||||||
|
pv, _ := foundBaseline["port-version"].(float64)
|
||||||
|
baselineVer = vcpkgBaselineVersionObjectEntry{
|
||||||
|
Baseline: bl,
|
||||||
|
PortVersion: pv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if baselineVer.Baseline == "" {
|
||||||
|
return nil, fmt.Errorf("could not find a baseline version for dependency with name %v", name)
|
||||||
|
}
|
||||||
|
if baselineVer.PortVersion > 0 {
|
||||||
|
finalVer = baselineVer.Baseline + "#" + strconv.Itoa(int(baselineVer.PortVersion))
|
||||||
|
} else {
|
||||||
|
finalVer = baselineVer.Baseline
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalVer = fullVersion
|
||||||
|
}
|
||||||
|
return getFsManifest(path, name, finalVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieves manifest file from the filesystem
|
||||||
|
func getFsManifest(path, name, ver string) (*Vcpkg, error) {
|
||||||
|
versionBytes, err := os.ReadFile(path + "/versions/" + name[0:1] + "-/" + name + ".json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var verFileCont vcpkgFsVersions
|
||||||
|
err = json.Unmarshal(versionBytes, &verFileCont)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, v := range verFileCont.Versions {
|
||||||
|
if v.GetFullVersion() == ver {
|
||||||
|
// the $ character can be used to reference the root of the registry
|
||||||
|
manifestPath := strings.ReplaceAll(v.Path, "$", path)
|
||||||
|
manBytes, err := os.ReadFile(manifestPath + "/vcpkg.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var vcpkgManRes Vcpkg
|
||||||
|
err = json.Unmarshal(manBytes, &vcpkgManRes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vcpkgManRes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to find vcpkg.json file for dependency name: %v", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vcpkg) BuildManifest(reg *pkg.VcpkgRegistryEntry, triplet string) *pkg.VcpkgManifest {
|
||||||
|
var desc []string
|
||||||
|
switch d := v.Description.(type) {
|
||||||
|
case string:
|
||||||
|
desc = append(desc, d)
|
||||||
|
case []any:
|
||||||
|
// json decodes a description array into []any, not []string
|
||||||
|
for _, item := range d {
|
||||||
|
if s, ok := item.(string); ok {
|
||||||
|
desc = append(desc, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// give each manifest its own registry copy: the resolver hands out a shared *VcpkgRegistryEntry
|
||||||
|
// (e.g. cfg.DefaultRegistry) to every package, and a shared pointer means one package's metadata
|
||||||
|
// can alias another's
|
||||||
|
var regCopy *pkg.VcpkgRegistryEntry
|
||||||
|
if reg != nil {
|
||||||
|
r := *reg
|
||||||
|
regCopy = &r
|
||||||
|
}
|
||||||
|
return &pkg.VcpkgManifest{
|
||||||
|
Description: desc,
|
||||||
|
Documentation: v.Documentation,
|
||||||
|
FullVersion: v.GetFullVersion(),
|
||||||
|
Version: v.GetPopulatedVersion(),
|
||||||
|
PortVersion: int(v.PortVersion),
|
||||||
|
Maintainers: v.Maintainers,
|
||||||
|
Name: v.Name,
|
||||||
|
Supports: v.Supports,
|
||||||
|
Registry: regCopy,
|
||||||
|
Triplet: triplet,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,15 @@
|
|||||||
package cpp
|
package cpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/cpp/internal/vcpkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type conanRef struct {
|
type conanRef struct {
|
||||||
@ -92,7 +96,7 @@ func newConanPackage(refStr string, metadata any, locations ...file.Location) *p
|
|||||||
Name: ref.Name,
|
Name: ref.Name,
|
||||||
Version: ref.Version,
|
Version: ref.Version,
|
||||||
Locations: file.NewLocationSet(locations...),
|
Locations: file.NewLocationSet(locations...),
|
||||||
PURL: packageURL(ref),
|
PURL: packageURLFromConanRef(ref),
|
||||||
Language: pkg.CPP,
|
Language: pkg.CPP,
|
||||||
Type: pkg.ConanPkg,
|
Type: pkg.ConanPkg,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
@ -103,7 +107,7 @@ func newConanPackage(refStr string, metadata any, locations ...file.Location) *p
|
|||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
func packageURL(ref *conanRef) string {
|
func packageURLFromConanRef(ref *conanRef) string {
|
||||||
qualifiers := packageurl.Qualifiers{}
|
qualifiers := packageurl.Qualifiers{}
|
||||||
if ref.Channel != "" {
|
if ref.Channel != "" {
|
||||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
@ -120,3 +124,63 @@ func packageURL(ref *conanRef) string {
|
|||||||
"",
|
"",
|
||||||
).ToString()
|
).ToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newVcpkgPackage(ctx context.Context, rm *vcpkg.ResolvedManifest, l file.Location) pkg.Package {
|
||||||
|
// build the SBOM metadata from the source manifest; license is a package-level field (pkg.Package),
|
||||||
|
// not metadata, so it is read off the source manifest here rather than carried in pkg.VcpkgManifest
|
||||||
|
man := rm.Vcpkg.BuildManifest(rm.Registry, "")
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: man.Name,
|
||||||
|
Version: man.FullVersion,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, rm.Vcpkg.License, l)),
|
||||||
|
Locations: file.NewLocationSet(l),
|
||||||
|
PURL: packageURLFromVcpkgManifest(man),
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: man,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetID()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func packageURLFromVcpkgManifest(v *pkg.VcpkgManifest) string {
|
||||||
|
qualifiers := packageurl.Qualifiers{}
|
||||||
|
//
|
||||||
|
if v.Triplet != "" {
|
||||||
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
|
Key: "triplet",
|
||||||
|
Value: v.Triplet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if v.PortVersion != 0 {
|
||||||
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
|
Key: "port_revision",
|
||||||
|
Value: strconv.Itoa(v.PortVersion),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if v.Registry != nil && v.Registry.Repository != "" {
|
||||||
|
unescRepoURL, err := url.PathUnescape(v.Registry.Repository)
|
||||||
|
if err == nil {
|
||||||
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
|
Key: "repository_url",
|
||||||
|
Value: unescRepoURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v.Registry != nil && v.Registry.Baseline != "" {
|
||||||
|
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||||
|
Key: "repository_revision",
|
||||||
|
Value: v.Registry.Baseline,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return packageurl.NewPackageURL(
|
||||||
|
// https://github.com/package-url/purl-spec/pull/245
|
||||||
|
"vcpkg",
|
||||||
|
"",
|
||||||
|
v.Name,
|
||||||
|
v.Version,
|
||||||
|
qualifiers,
|
||||||
|
"",
|
||||||
|
).ToString()
|
||||||
|
}
|
||||||
|
|||||||
182
syft/pkg/cataloger/cpp/parse_vcpkgmanifest.go
Normal file
182
syft/pkg/cataloger/cpp/parse_vcpkgmanifest.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package cpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/cpp/internal/vcpkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// vcpkg manifests are small JSON files; cap reads to guard against pathological inputs
|
||||||
|
const maxVcpkgManifestSize = 5 * 1024 * 1024
|
||||||
|
|
||||||
|
type vcpkgCataloger struct {
|
||||||
|
allowGitClone bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVcpkgCataloger(allowGitClone bool) *vcpkgCataloger {
|
||||||
|
return &vcpkgCataloger{
|
||||||
|
allowGitClone: allowGitClone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parser is for vcpkg in "Manifest" mode. This is opposed to "Classic" mode which or is more akin to a system package manager. (https://learn.microsoft.com/en-us/vcpkg/concepts/classic-mode)
|
||||||
|
func (v *vcpkgCataloger) parseVcpkgManifest(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
conf, err := findVcpkgConfig(resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("something went wrong parsing vcpkg-configuration.json file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var toplevelVcpkg vcpkg.Vcpkg
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
err = dec.Decode(&toplevelVcpkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to parse vcpkg.json file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var vcpkgs []vcpkg.Vcpkg
|
||||||
|
vcpkgs = append(vcpkgs, toplevelVcpkg)
|
||||||
|
overlayVcpkgs, err := findOverlayManifests(resolver, conf.OverlayPorts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not get overlay port manifests: %w", err)
|
||||||
|
}
|
||||||
|
vcpkgs = append(vcpkgs, overlayVcpkgs...)
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
var relationships []artifact.Relationship
|
||||||
|
for _, parentVcpkg := range vcpkgs {
|
||||||
|
pv := parentVcpkg
|
||||||
|
// the top-level project manifest has no registry (it is the thing being scanned, not a resolved dep)
|
||||||
|
parentMan := &vcpkg.ResolvedManifest{Vcpkg: &pv, Registry: nil}
|
||||||
|
pPkg := newVcpkgPackage(ctx, parentMan, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||||
|
pkgs = append(
|
||||||
|
pkgs,
|
||||||
|
pPkg)
|
||||||
|
|
||||||
|
r := vcpkg.NewResolver(
|
||||||
|
ctx,
|
||||||
|
conf,
|
||||||
|
v.allowGitClone,
|
||||||
|
)
|
||||||
|
for _, dep := range parentVcpkg.Dependencies {
|
||||||
|
cMans, fetchErr := r.FindManifests(dep, true, toplevelVcpkg.BuiltinBaseline, toplevelVcpkg.Overrides, parentMan)
|
||||||
|
if fetchErr != nil {
|
||||||
|
// best-effort: a single unresolvable dependency shouldn't discard packages already found
|
||||||
|
log.Debugf("vcpkg: unable to resolve dependency in %q: %v", parentVcpkg.Name, fetchErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgs, relationships = appendPkgsAndRelationships(ctx, cMans, overlayVcpkgs, reader, relationships, pkgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pkg.Sort(pkgs)
|
||||||
|
return pkgs, relationships, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendPkgsAndRelationships(ctx context.Context, cMans []vcpkg.ManifestNode, overlayVcpkgs []vcpkg.Vcpkg, reader file.LocationReadCloser, relationships []artifact.Relationship, pkgs []pkg.Package) ([]pkg.Package, []artifact.Relationship) {
|
||||||
|
p := pkgs
|
||||||
|
r := relationships
|
||||||
|
for _, c := range cMans {
|
||||||
|
if c.Child != nil && !hasBeenOverlayed(c.Child.Vcpkg.Name, overlayVcpkgs) {
|
||||||
|
cPkg := newVcpkgPackage(ctx, c.Child, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||||
|
if c.Parent != nil {
|
||||||
|
pPkg := newVcpkgPackage(ctx, c.Parent, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||||
|
pPkg.FoundBy = "vcpkg-manifest-cataloger"
|
||||||
|
cPkg.FoundBy = "vcpkg-manifest-cataloger"
|
||||||
|
rship := artifact.Relationship{
|
||||||
|
// From is the dependency, To is the dependent (syft convention)
|
||||||
|
From: cPkg,
|
||||||
|
To: pPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
}
|
||||||
|
r = append(
|
||||||
|
r,
|
||||||
|
rship)
|
||||||
|
}
|
||||||
|
p = append(
|
||||||
|
p,
|
||||||
|
cPkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, r
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are to be used in place of dependencies with the same name
|
||||||
|
func findOverlayManifests(resolver file.Resolver, overlayPorts []string) ([]vcpkg.Vcpkg, error) {
|
||||||
|
var manifests []vcpkg.Vcpkg
|
||||||
|
for _, op := range overlayPorts {
|
||||||
|
// overlay port path is relative to location of vcpkg-configuration.json file
|
||||||
|
locs, err := resolver.FilesByGlob(op + "/**/vcpkg.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, loc := range locs {
|
||||||
|
man, err := findManAtLoc(loc, resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
manifests = append(manifests, *man)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return manifests, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findManAtLoc(loc file.Location, resolver file.Resolver) (*vcpkg.Vcpkg, error) {
|
||||||
|
manCont, err := resolver.FileContentsByLocation(loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(manCont, loc.RealPath)
|
||||||
|
manBytes, err := io.ReadAll(io.LimitReader(manCont, maxVcpkgManifestSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var man vcpkg.Vcpkg
|
||||||
|
err = json.Unmarshal(manBytes, &man)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &man, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see if a package is not going to get pulled in because it's apart of the overlay-ports in vcpkg-configuration.json file
|
||||||
|
func hasBeenOverlayed(pkgName string, overlayMans []vcpkg.Vcpkg) bool {
|
||||||
|
for _, om := range overlayMans {
|
||||||
|
if om.Name == pkgName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// needed to know what vcpkg registries to use for what packages when looking for manifest files
|
||||||
|
func findVcpkgConfig(resolver file.Resolver) (*vcpkg.Config, error) {
|
||||||
|
locs, err := resolver.FilesByGlob("**/vcpkg-configuration.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(locs) != 0 {
|
||||||
|
cfgCont, err := resolver.FileContentsByLocation(locs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer internal.CloseAndLogError(cfgCont, locs[0].RealPath)
|
||||||
|
cfgBytes, err := io.ReadAll(io.LimitReader(cfgCont, maxVcpkgManifestSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var vcpkgConf vcpkg.Config
|
||||||
|
err = json.Unmarshal(cfgBytes, &vcpkgConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vcpkgConf, err
|
||||||
|
}
|
||||||
|
return &vcpkg.Config{}, nil
|
||||||
|
}
|
||||||
244
syft/pkg/cataloger/cpp/parse_vcpkgmanifest_test.go
Normal file
244
syft/pkg/cataloger/cpp/parse_vcpkgmanifest_test.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package cpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/cache"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseVcpkgManifest(t *testing.T) {
|
||||||
|
|
||||||
|
fixture := "testdata/vcpkg/helloworld"
|
||||||
|
// the resolver reads the vcpkg registry from a local git clone rather than cloning over the network
|
||||||
|
// at test time. the clone is materialized just-in-time on first run (network required once) into a
|
||||||
|
// gitignored cache dir, then reused offline on later runs.
|
||||||
|
useLocalVcpkgRegistryCache(t)
|
||||||
|
fileLocs := []file.Location{file.NewLocation("vcpkg.json")}
|
||||||
|
fixtureLocationSet := file.NewLocationSet(fileLocs...)
|
||||||
|
ctx := pkgtest.Context(t)
|
||||||
|
|
||||||
|
fmtPkg := pkg.Package{
|
||||||
|
Name: "fmt",
|
||||||
|
Version: "11.0.2#1",
|
||||||
|
PURL: "pkg:vcpkg/fmt@11.0.2?port_revision=1&repository_revision=fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3&repository_url=https%3A%2F%2Fgithub.com%2Fanchore%2Fvcpkg-test-fixture",
|
||||||
|
FoundBy: "vcpkg-manifest-cataloger",
|
||||||
|
Locations: fixtureLocationSet,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", fileLocs...)),
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: &pkg.VcpkgManifest{
|
||||||
|
Description: []string{"{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams."},
|
||||||
|
FullVersion: "11.0.2#1",
|
||||||
|
Version: "11.0.2",
|
||||||
|
PortVersion: 1,
|
||||||
|
Name: "fmt",
|
||||||
|
Registry: &pkg.VcpkgRegistryEntry{
|
||||||
|
Baseline: "fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3",
|
||||||
|
Kind: pkg.Git,
|
||||||
|
Repository: "https://github.com/anchore/vcpkg-test-fixture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
helloPkg := pkg.Package{
|
||||||
|
Name: "hello",
|
||||||
|
Version: "0.1.0",
|
||||||
|
PURL: "pkg:vcpkg/hello@0.1.0",
|
||||||
|
FoundBy: "vcpkg-manifest-cataloger",
|
||||||
|
Locations: fixtureLocationSet,
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: &pkg.VcpkgManifest{
|
||||||
|
FullVersion: "0.1.0",
|
||||||
|
Version: "0.1.0",
|
||||||
|
Name: "hello",
|
||||||
|
Registry: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
vcpkgCmakeConfigPkg := pkg.Package{
|
||||||
|
Name: "vcpkg-cmake-config",
|
||||||
|
Version: "2024-05-23",
|
||||||
|
PURL: "pkg:vcpkg/vcpkg-cmake-config@2024-05-23?repository_revision=fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3&repository_url=https%3A%2F%2Fgithub.com%2Fanchore%2Fvcpkg-test-fixture",
|
||||||
|
FoundBy: "vcpkg-manifest-cataloger",
|
||||||
|
Locations: fixtureLocationSet,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", fileLocs...)),
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: &pkg.VcpkgManifest{
|
||||||
|
Documentation: "https://learn.microsoft.com/vcpkg/maintainers/functions/vcpkg_cmake_config_fixup",
|
||||||
|
FullVersion: "2024-05-23",
|
||||||
|
Version: "2024-05-23",
|
||||||
|
Name: "vcpkg-cmake-config",
|
||||||
|
Registry: &pkg.VcpkgRegistryEntry{
|
||||||
|
Baseline: "fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3",
|
||||||
|
Kind: pkg.Git,
|
||||||
|
Repository: "https://github.com/anchore/vcpkg-test-fixture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
vcpkgCmakePkg := pkg.Package{
|
||||||
|
Name: "vcpkg-cmake",
|
||||||
|
Version: "2024-04-23",
|
||||||
|
PURL: "pkg:vcpkg/vcpkg-cmake@2024-04-23?repository_revision=fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3&repository_url=https%3A%2F%2Fgithub.com%2Fanchore%2Fvcpkg-test-fixture",
|
||||||
|
FoundBy: "vcpkg-manifest-cataloger",
|
||||||
|
Locations: fixtureLocationSet,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", fileLocs...)),
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: &pkg.VcpkgManifest{
|
||||||
|
Documentation: "https://learn.microsoft.com/vcpkg/maintainers/functions/vcpkg_cmake_configure",
|
||||||
|
FullVersion: "2024-04-23",
|
||||||
|
Version: "2024-04-23",
|
||||||
|
Name: "vcpkg-cmake",
|
||||||
|
Registry: &pkg.VcpkgRegistryEntry{
|
||||||
|
Baseline: "fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3",
|
||||||
|
Kind: pkg.Git,
|
||||||
|
Repository: "https://github.com/anchore/vcpkg-test-fixture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sampleLibPkg := pkg.Package{
|
||||||
|
Name: "vcpkg-sample-library",
|
||||||
|
Version: "1.0.2",
|
||||||
|
PURL: "pkg:vcpkg/vcpkg-sample-library@1.0.2?repository_revision=fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3&repository_url=https%3A%2F%2Fgithub.com%2Fanchore%2Fvcpkg-test-fixture",
|
||||||
|
FoundBy: "vcpkg-manifest-cataloger",
|
||||||
|
Locations: fixtureLocationSet,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", fileLocs...)),
|
||||||
|
Language: pkg.CPP,
|
||||||
|
Type: pkg.VcpkgPkg,
|
||||||
|
Metadata: &pkg.VcpkgManifest{
|
||||||
|
Description: []string{"A sample C++ library designed to serve as a foundational example for a tutorial on packaging libraries with vcpkg."},
|
||||||
|
FullVersion: "1.0.2",
|
||||||
|
Version: "1.0.2",
|
||||||
|
Name: "vcpkg-sample-library",
|
||||||
|
Registry: &pkg.VcpkgRegistryEntry{
|
||||||
|
Baseline: "fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3",
|
||||||
|
Kind: pkg.Git,
|
||||||
|
Repository: "https://github.com/anchore/vcpkg-test-fixture",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
fmtPkg,
|
||||||
|
fmtPkg,
|
||||||
|
helloPkg,
|
||||||
|
vcpkgCmakeConfigPkg,
|
||||||
|
vcpkgCmakeConfigPkg,
|
||||||
|
vcpkgCmakePkg,
|
||||||
|
vcpkgCmakePkg,
|
||||||
|
sampleLibPkg,
|
||||||
|
}
|
||||||
|
|
||||||
|
// relationships require IDs to be set to be sorted similarly
|
||||||
|
for i := range expectedPkgs {
|
||||||
|
expectedPkgs[i].SetID()
|
||||||
|
}
|
||||||
|
pkg.Sort(expectedPkgs)
|
||||||
|
|
||||||
|
expectedRelationships := []artifact.Relationship{
|
||||||
|
{
|
||||||
|
From: fmtPkg,
|
||||||
|
To: helloPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: vcpkgCmakePkg,
|
||||||
|
To: fmtPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: vcpkgCmakeConfigPkg,
|
||||||
|
To: fmtPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: sampleLibPkg,
|
||||||
|
To: helloPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: fmtPkg,
|
||||||
|
To: sampleLibPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: vcpkgCmakePkg,
|
||||||
|
To: sampleLibPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
From: vcpkgCmakeConfigPkg,
|
||||||
|
To: sampleLibPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogerCfg := CatalogerConfig{
|
||||||
|
VcpkgAllowGitClone: false,
|
||||||
|
}
|
||||||
|
pkgtest.TestCataloger(t, fixture, NewVcpkgManifestCataloger(catalogerCfg), expectedPkgs, expectedRelationships)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the registry the helloworld fixture pins; see testdata/vcpkg/helloworld/vcpkg-configuration.json
|
||||||
|
const vcpkgTestRegistryURL = "https://github.com/anchore/vcpkg-test-fixture"
|
||||||
|
|
||||||
|
// useLocalVcpkgRegistryCache points syft's cache manager at a gitignored testdata cache and ensures a local
|
||||||
|
// clone of the vcpkg test registry exists there, so the resolver resolves it offline via git.PlainOpen
|
||||||
|
// (getVcpkgGitCachePath resolves to "<syft cache root>/../vcpkg/registries/git").
|
||||||
|
func useLocalVcpkgRegistryCache(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cacheRoot := filepath.Join("testdata", "cache")
|
||||||
|
syftCacheRoot := filepath.Join(cacheRoot, "syft")
|
||||||
|
registryGitDir := filepath.Join(cacheRoot, "vcpkg", "registries", "git")
|
||||||
|
|
||||||
|
prepareVcpkgRegistryCache(t, registryGitDir)
|
||||||
|
|
||||||
|
mgr, err := cache.NewFromDir(syftCacheRoot, 24*time.Hour)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prev := cache.GetManager()
|
||||||
|
cache.SetManager(mgr)
|
||||||
|
t.Cleanup(func() { cache.SetManager(prev) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareVcpkgRegistryCache clones the vcpkg test registry into gitDir if a valid clone is not already present.
|
||||||
|
// the clone happens once (first run, requires network); later runs reuse it offline.
|
||||||
|
func prepareVcpkgRegistryCache(t *testing.T, gitDir string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if _, err := git.PlainOpen(gitDir); err == nil {
|
||||||
|
return // valid cache already present
|
||||||
|
}
|
||||||
|
// clear any partial/stale state left by an interrupted run
|
||||||
|
require.NoError(t, os.RemoveAll(gitDir))
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(gitDir), 0o755))
|
||||||
|
|
||||||
|
// clone into a temp sibling then atomically move into place so an interrupted or concurrent run can't
|
||||||
|
// observe a half-written cache
|
||||||
|
tmp, err := os.MkdirTemp(filepath.Dir(gitDir), "clone-")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
|
cloneTarget := filepath.Join(tmp, "git")
|
||||||
|
if _, err := git.PlainClone(cloneTarget, false, &git.CloneOptions{URL: vcpkgTestRegistryURL}); err != nil {
|
||||||
|
t.Skipf("vcpkg registry cache is not present and could not be prepared (first run requires network): %v", err)
|
||||||
|
}
|
||||||
|
require.NoError(t, os.Rename(cloneTarget, gitDir))
|
||||||
|
}
|
||||||
2
syft/pkg/cataloger/cpp/testdata/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/cpp/testdata/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/cache/
|
||||||
|
test-observations.json
|
||||||
15
syft/pkg/cataloger/cpp/testdata/README.md
vendored
15
syft/pkg/cataloger/cpp/testdata/README.md
vendored
@ -1,8 +1,10 @@
|
|||||||
# Conan test data
|
# C++ test data
|
||||||
|
|
||||||
This folder contains the test data for the Conan package manager.
|
This folder contains the test data for the Conan and Vcpkg package managers.
|
||||||
|
|
||||||
## conan.lock
|
## Conan test data
|
||||||
|
|
||||||
|
### conan.lock
|
||||||
|
|
||||||
The conan lock file is created in the following way.
|
The conan lock file is created in the following way.
|
||||||
|
|
||||||
@ -14,4 +16,9 @@ This is necessary to verify that the dependency tree is properly parsed.
|
|||||||
`sed -i 's|mfast/1.2.2#c6f6387c9b99780f0ee05e25f99d0f39|mfast/1.2.2@my_user/my_channel#c6f6387c9b99780f0ee05e25f99d0f39|g' conan.lock`
|
`sed -i 's|mfast/1.2.2#c6f6387c9b99780f0ee05e25f99d0f39|mfast/1.2.2@my_user/my_channel#c6f6387c9b99780f0ee05e25f99d0f39|g' conan.lock`
|
||||||
3. Manually delete the package id and prev from tinyxml2 entry to test conan lock parsing if they are missing:
|
3. Manually delete the package id and prev from tinyxml2 entry to test conan lock parsing if they are missing:
|
||||||
`sed -i 's|\"package_id\": \"6557f18ca99c0b6a233f43db00e30efaa525e27e\",||g' conan.lock`
|
`sed -i 's|\"package_id\": \"6557f18ca99c0b6a233f43db00e30efaa525e27e\",||g' conan.lock`
|
||||||
`sed -i 's|\"prev\": \"548bb273d2980991baa519453d68e5cd\",||g' conan.lock`
|
`sed -i 's|\"prev\": \"548bb273d2980991baa519453d68e5cd\",||g' conan.lock`
|
||||||
|
|
||||||
|
## Vcpkg test data
|
||||||
|
|
||||||
|
vcpkg/helloworld contains a vcpkg project that contains only the files necessary to retrieve all of the manifests for dependencies listed
|
||||||
|
in the vcpkg.json file. These manifests exist in a remote vcpkg git registry located at https://github.com/anchore/vcpkg-test-fixture
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
syft/pkg/cataloger/cpp/testdata/glob-paths/somewhere/src/vcpkg-configuration.json
vendored
Normal file
1
syft/pkg/cataloger/cpp/testdata/glob-paths/somewhere/src/vcpkg-configuration.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
1
syft/pkg/cataloger/cpp/testdata/glob-paths/somewhere/src/vcpkg.json
vendored
Normal file
1
syft/pkg/cataloger/cpp/testdata/glob-paths/somewhere/src/vcpkg.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
7
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/helloworld.cpp
vendored
Normal file
7
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/helloworld.cpp
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
fmt::print("Hello World!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
7
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/vcpkg-configuration.json
vendored
Normal file
7
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/vcpkg-configuration.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"default-registry": {
|
||||||
|
"kind": "git",
|
||||||
|
"baseline": "fbfe5a93a4b9562d88dcbc9cefca0016594ba3b3",
|
||||||
|
"repository": "https://github.com/anchore/vcpkg-test-fixture"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/vcpkg.json
vendored
Normal file
10
syft/pkg/cataloger/cpp/testdata/vcpkg/helloworld/vcpkg.json
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "hello",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"name": "fmt"
|
||||||
|
},
|
||||||
|
"vcpkg-sample-library"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -52,6 +52,7 @@ const (
|
|||||||
SwiftPkg Type = "swift"
|
SwiftPkg Type = "swift"
|
||||||
SwiplPackPkg Type = "swiplpack"
|
SwiplPackPkg Type = "swiplpack"
|
||||||
TerraformPkg Type = "terraform"
|
TerraformPkg Type = "terraform"
|
||||||
|
VcpkgPkg Type = "vcpkg"
|
||||||
WordpressPluginPkg Type = "wordpress-plugin"
|
WordpressPluginPkg Type = "wordpress-plugin"
|
||||||
HomebrewPkg Type = "homebrew"
|
HomebrewPkg Type = "homebrew"
|
||||||
AppleAppBundlePkg Type = "apple-app-bundle"
|
AppleAppBundlePkg Type = "apple-app-bundle"
|
||||||
@ -98,6 +99,7 @@ var AllPkgs = []Type{
|
|||||||
SwiftPkg,
|
SwiftPkg,
|
||||||
SwiplPackPkg,
|
SwiplPackPkg,
|
||||||
TerraformPkg,
|
TerraformPkg,
|
||||||
|
VcpkgPkg,
|
||||||
WordpressPluginPkg,
|
WordpressPluginPkg,
|
||||||
HomebrewPkg,
|
HomebrewPkg,
|
||||||
AppleAppBundlePkg,
|
AppleAppBundlePkg,
|
||||||
@ -174,6 +176,8 @@ func (t Type) PackageURLType() string {
|
|||||||
return "swiplpack"
|
return "swiplpack"
|
||||||
case TerraformPkg:
|
case TerraformPkg:
|
||||||
return "terraform"
|
return "terraform"
|
||||||
|
case VcpkgPkg:
|
||||||
|
return "vcpkg"
|
||||||
case WordpressPluginPkg:
|
case WordpressPluginPkg:
|
||||||
return "wordpress-plugin"
|
return "wordpress-plugin"
|
||||||
case HomebrewPkg:
|
case HomebrewPkg:
|
||||||
@ -262,6 +266,8 @@ func TypeByName(name string) Type {
|
|||||||
return SwiplPackPkg
|
return SwiplPackPkg
|
||||||
case "terraform":
|
case "terraform":
|
||||||
return TerraformPkg
|
return TerraformPkg
|
||||||
|
case "vcpkg":
|
||||||
|
return VcpkgPkg
|
||||||
case "wordpress-plugin":
|
case "wordpress-plugin":
|
||||||
return WordpressPluginPkg
|
return WordpressPluginPkg
|
||||||
case "homebrew":
|
case "homebrew":
|
||||||
|
|||||||
@ -163,6 +163,7 @@ func TestTypeFromPURL(t *testing.T) {
|
|||||||
expectedTypes.Remove(string(ModelPkg)) // no valid purl for ai artifacts currently
|
expectedTypes.Remove(string(ModelPkg)) // no valid purl for ai artifacts currently
|
||||||
expectedTypes.Remove(string(AppleAppBundlePkg)) // no standard purl type for apple app bundles
|
expectedTypes.Remove(string(AppleAppBundlePkg)) // no standard purl type for apple app bundles
|
||||||
expectedTypes.Remove(string(PhpPeclPkg)) // we should always consider this a pear package
|
expectedTypes.Remove(string(PhpPeclPkg)) // we should always consider this a pear package
|
||||||
|
expectedTypes.Remove(string(VcpkgPkg))
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(string(test.expected), func(t *testing.T) {
|
t.Run(string(test.expected), func(t *testing.T) {
|
||||||
|
|||||||
35
syft/pkg/vcpkg.go
Normal file
35
syft/pkg/vcpkg.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
type VcpkgRegistryKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileSystem VcpkgRegistryKind = "filesystem"
|
||||||
|
Git VcpkgRegistryKind = "git"
|
||||||
|
Builtin VcpkgRegistryKind = "builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// used for metadata. Includes summary of data found in manifest (vcpkg.json file) relevant to just that vcpkg
|
||||||
|
type VcpkgManifest struct {
|
||||||
|
Description []string `json:"description,omitempty"`
|
||||||
|
Documentation string `json:"documentation,omitempty"`
|
||||||
|
FullVersion string `json:"full-version"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
PortVersion int `json:"port-version"`
|
||||||
|
Maintainers []string `json:"maintainers,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Supports string `json:"supports,omitempty"`
|
||||||
|
// to show where it came from
|
||||||
|
Registry *VcpkgRegistryEntry `json:"registry,omitempty"`
|
||||||
|
// found by looking at build folder to find target. ex. "x64-linux"
|
||||||
|
Triplet string `json:"triplet,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches definition of Vcpkg "Registry". https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-configuration-json#registry
|
||||||
|
type VcpkgRegistryEntry struct {
|
||||||
|
Baseline string `json:"baseline,omitempty"`
|
||||||
|
Kind VcpkgRegistryKind `json:"kind"`
|
||||||
|
Packages []string `json:"packages,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
Reference string `json:"reference,omitempty"`
|
||||||
|
Repository string `json:"repository,omitempty"`
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user