feat: emit dependency relationships found in Cargo.lock (#3443)

* feat: emit dependency relationships found in Cargo.lock

Include updating test Cargo.lock to have dependencies on multiple
versions of the same crate.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
This commit is contained in:
William Murphy 2024-11-14 16:45:00 -05:00 committed by GitHub
parent 926486a7c3
commit bc35345afb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 363 additions and 171 deletions

View File

@ -33,19 +33,50 @@ func parseCargoLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
} }
var pkgs []pkg.Package var pkgs []pkg.Package
pkgIndex := make(map[string]int)
for _, p := range m.Packages { for _, p := range m.Packages {
if p.Dependencies == nil { if p.Dependencies == nil {
p.Dependencies = make([]string, 0) p.Dependencies = make([]string, 0)
} }
pkgs = append( newPkg := newPackageFromCargoMetadata(
pkgs,
newPackageFromCargoMetadata(
p, p,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
) )
pkgs = append(
pkgs,
newPkg,
)
newIx := len(pkgs) - 1
// Cargo.lock dependencies are strings that are the name of a package, if that
// is unambiguous, or a string like "name version" if the name alone is not
// ambiguous. Set both keys in the map, since we don't know which key is
// going to be used until we're trying to resolve dependencies. If the
// first key is overwritten, that means the package name was an ambiguous dependency
// and "name version" will be used as the key anyway.
keys := []string{
newPkg.Name,
fmt.Sprintf("%s %s", newPkg.Name, newPkg.Version),
}
for _, k := range keys {
pkgIndex[k] = newIx
}
}
var relationships []artifact.Relationship
for _, p := range pkgs {
meta := p.Metadata.(pkg.RustCargoLockEntry)
for _, d := range meta.Dependencies {
i, ok := pkgIndex[d]
if !ok {
continue
}
relationships = append(relationships, artifact.Relationship{
From: p,
To: pkgs[i],
Type: artifact.DependencyOfRelationship,
})
}
} }
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages") return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
} }

View File

@ -12,8 +12,7 @@ import (
func TestParseCargoLock(t *testing.T) { func TestParseCargoLock(t *testing.T) {
fixture := "test-fixtures/Cargo.lock" fixture := "test-fixtures/Cargo.lock"
locations := file.NewLocationSet(file.NewLocation(fixture)) locations := file.NewLocationSet(file.NewLocation(fixture))
expectedPkgs := []pkg.Package{ ansiTerm := pkg.Package{
{
Name: "ansi_term", Name: "ansi_term",
Version: "0.12.1", Version: "0.12.1",
PURL: "pkg:cargo/ansi_term@0.12.1", PURL: "pkg:cargo/ansi_term@0.12.1",
@ -30,8 +29,26 @@ func TestParseCargoLock(t *testing.T) {
"winapi", "winapi",
}, },
}, },
}
errno := pkg.Package{
Name: "errno",
Version: "0.3.9",
PURL: "pkg:cargo/errno@0.3.9",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "errno",
Version: "0.3.9",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba",
Dependencies: []string{
"windows-sys 0.52.0",
}, },
{ },
}
matches := pkg.Package{
Name: "matches", Name: "matches",
Version: "0.1.8", Version: "0.1.8",
PURL: "pkg:cargo/matches@0.1.8", PURL: "pkg:cargo/matches@0.1.8",
@ -46,8 +63,8 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08", Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08",
Dependencies: []string{}, Dependencies: []string{},
}, },
}, }
{ memchr := pkg.Package{
Name: "memchr", Name: "memchr",
Version: "2.3.3", Version: "2.3.3",
PURL: "pkg:cargo/memchr@2.3.3", PURL: "pkg:cargo/memchr@2.3.3",
@ -62,8 +79,9 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400", Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400",
Dependencies: []string{}, Dependencies: []string{},
}, },
}, }
{
natord := pkg.Package{
Name: "natord", Name: "natord",
Version: "1.0.9", Version: "1.0.9",
PURL: "pkg:cargo/natord@1.0.9", PURL: "pkg:cargo/natord@1.0.9",
@ -78,8 +96,9 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c", Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c",
Dependencies: []string{}, Dependencies: []string{},
}, },
}, }
{
nom := pkg.Package{
Name: "nom", Name: "nom",
Version: "4.2.3", Version: "4.2.3",
PURL: "pkg:cargo/nom@4.2.3", PURL: "pkg:cargo/nom@4.2.3",
@ -97,8 +116,27 @@ func TestParseCargoLock(t *testing.T) {
"version_check", "version_check",
}, },
}, },
}
schannel := pkg.Package{
Name: "schannel",
Version: "0.1.26",
PURL: "pkg:cargo/schannel@0.1.26",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "schannel",
Version: "0.1.26",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1",
Dependencies: []string{
"windows-sys 0.59.0",
}, },
{ },
}
unicodeBidi := pkg.Package{
Name: "unicode-bidi", Name: "unicode-bidi",
Version: "0.3.4", Version: "0.3.4",
PURL: "pkg:cargo/unicode-bidi@0.3.4", PURL: "pkg:cargo/unicode-bidi@0.3.4",
@ -113,10 +151,12 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5", Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5",
Dependencies: []string{ Dependencies: []string{
"matches", "matches",
"bogus", // a bad dependency to test error handling
}, },
}, },
}, }
{
versionCheck := pkg.Package{
Name: "version_check", Name: "version_check",
Version: "0.1.5", Version: "0.1.5",
PURL: "pkg:cargo/version_check@0.1.5", PURL: "pkg:cargo/version_check@0.1.5",
@ -131,8 +171,9 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd", Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd",
Dependencies: []string{}, Dependencies: []string{},
}, },
}, }
{
winapi := pkg.Package{
Name: "winapi", Name: "winapi",
Version: "0.3.9", Version: "0.3.9",
PURL: "pkg:cargo/winapi@0.3.9", PURL: "pkg:cargo/winapi@0.3.9",
@ -150,8 +191,9 @@ func TestParseCargoLock(t *testing.T) {
"winapi-x86_64-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu",
}, },
}, },
}, }
{
winAPIi686PCWindowsGNU := pkg.Package{
Name: "winapi-i686-pc-windows-gnu", Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0", Version: "0.4.0",
PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0", PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0",
@ -166,8 +208,9 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6", Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6",
Dependencies: []string{}, Dependencies: []string{},
}, },
}, }
{
winAPIx8664PCWindowsGNU := pkg.Package{
Name: "winapi-x86_64-pc-windows-gnu", Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0", Version: "0.4.0",
PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0", PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0",
@ -182,14 +225,102 @@ func TestParseCargoLock(t *testing.T) {
Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f", Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f",
Dependencies: []string{}, Dependencies: []string{},
}, },
}
windowsSys52 := pkg.Package{
Name: "windows-sys",
Version: "0.52.0",
PURL: "pkg:cargo/windows-sys@0.52.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "windows-sys",
Version: "0.52.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d",
Dependencies: []string{},
}, },
} }
// TODO: no relationships are under test yet windowsSys59 := pkg.Package{
var expectedRelationships []artifact.Relationship Name: "windows-sys",
Version: "0.59.0",
PURL: "pkg:cargo/windows-sys@0.59.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "windows-sys",
Version: "0.59.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b",
Dependencies: []string{},
},
}
expectedPkgs := []pkg.Package{
ansiTerm,
errno,
matches,
memchr,
natord,
nom,
schannel,
unicodeBidi,
versionCheck,
winapi,
winAPIi686PCWindowsGNU,
winAPIx8664PCWindowsGNU,
windowsSys52,
windowsSys59,
}
expectedRelationships := []artifact.Relationship{
{
From: ansiTerm,
To: winapi,
Type: artifact.DependencyOfRelationship,
},
{
From: errno,
To: windowsSys52,
Type: artifact.DependencyOfRelationship,
},
{
From: nom,
To: memchr,
Type: artifact.DependencyOfRelationship,
},
{
From: nom,
To: versionCheck,
Type: artifact.DependencyOfRelationship,
},
{
From: schannel,
To: windowsSys59,
Type: artifact.DependencyOfRelationship,
},
{
From: unicodeBidi,
To: matches,
Type: artifact.DependencyOfRelationship,
},
{
From: winapi,
To: winAPIi686PCWindowsGNU,
Type: artifact.DependencyOfRelationship,
},
{
From: winapi,
To: winAPIx8664PCWindowsGNU,
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
} }
func Test_corruptCargoLock(t *testing.T) { func Test_corruptCargoLock(t *testing.T) {

View File

@ -9,6 +9,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.8" version = "0.1.8"
@ -37,6 +46,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "schannel"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.4"
@ -44,6 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [ dependencies = [
"matches", "matches",
"bogus", # not present in cargo.lock; test error handling
] ]
[[package]] [[package]]
@ -74,3 +93,14 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"