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)
} }
newPkg := newPackageFromCargoMetadata(
p,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)
pkgs = append( pkgs = append(
pkgs, pkgs,
newPackageFromCargoMetadata( newPkg,
p,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
) )
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,184 +12,315 @@ 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", Locations: locations,
Locations: locations, Language: pkg.Rust,
Language: pkg.Rust, Type: pkg.RustPkg,
Type: pkg.RustPkg, Licenses: pkg.NewLicenseSet(),
Licenses: pkg.NewLicenseSet(), Metadata: pkg.RustCargoLockEntry{
Metadata: pkg.RustCargoLockEntry{ Name: "ansi_term",
Name: "ansi_term", Version: "0.12.1",
Version: "0.12.1", Source: "registry+https://github.com/rust-lang/crates.io-index",
Source: "registry+https://github.com/rust-lang/crates.io-index", Checksum: "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2",
Checksum: "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2", Dependencies: []string{
Dependencies: []string{ "winapi",
"winapi",
},
}, },
}, },
{ }
Name: "matches", errno := pkg.Package{
Version: "0.1.8", Name: "errno",
PURL: "pkg:cargo/matches@0.1.8", Version: "0.3.9",
Locations: locations, PURL: "pkg:cargo/errno@0.3.9",
Language: pkg.Rust, Locations: locations,
Type: pkg.RustPkg, Language: pkg.Rust,
Licenses: pkg.NewLicenseSet(), Type: pkg.RustPkg,
Metadata: pkg.RustCargoLockEntry{ Licenses: pkg.NewLicenseSet(),
Name: "matches", Metadata: pkg.RustCargoLockEntry{
Version: "0.1.8", Name: "errno",
Source: "registry+https://github.com/rust-lang/crates.io-index", Version: "0.3.9",
Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08", Source: "registry+https://github.com/rust-lang/crates.io-index",
Dependencies: []string{}, Checksum: "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba",
Dependencies: []string{
"windows-sys 0.52.0",
}, },
}, },
{ }
Name: "memchr", matches := pkg.Package{
Version: "2.3.3", Name: "matches",
PURL: "pkg:cargo/memchr@2.3.3", Version: "0.1.8",
Locations: locations, PURL: "pkg:cargo/matches@0.1.8",
Language: pkg.Rust, Locations: locations,
Type: pkg.RustPkg, Language: pkg.Rust,
Licenses: pkg.NewLicenseSet(), Type: pkg.RustPkg,
Metadata: pkg.RustCargoLockEntry{ Licenses: pkg.NewLicenseSet(),
Name: "memchr", Metadata: pkg.RustCargoLockEntry{
Version: "2.3.3", Name: "matches",
Source: "registry+https://github.com/rust-lang/crates.io-index", Version: "0.1.8",
Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400", Source: "registry+https://github.com/rust-lang/crates.io-index",
Dependencies: []string{}, Checksum: "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08",
Dependencies: []string{},
},
}
memchr := pkg.Package{
Name: "memchr",
Version: "2.3.3",
PURL: "pkg:cargo/memchr@2.3.3",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "memchr",
Version: "2.3.3",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400",
Dependencies: []string{},
},
}
natord := pkg.Package{
Name: "natord",
Version: "1.0.9",
PURL: "pkg:cargo/natord@1.0.9",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "natord",
Version: "1.0.9",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c",
Dependencies: []string{},
},
}
nom := pkg.Package{
Name: "nom",
Version: "4.2.3",
PURL: "pkg:cargo/nom@4.2.3",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "nom",
Version: "4.2.3",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6",
Dependencies: []string{
"memchr",
"version_check",
}, },
}, },
{ }
Name: "natord", schannel := pkg.Package{
Version: "1.0.9", Name: "schannel",
PURL: "pkg:cargo/natord@1.0.9", Version: "0.1.26",
Locations: locations, PURL: "pkg:cargo/schannel@0.1.26",
Language: pkg.Rust, Locations: locations,
Type: pkg.RustPkg, Language: pkg.Rust,
Licenses: pkg.NewLicenseSet(), Type: pkg.RustPkg,
Metadata: pkg.RustCargoLockEntry{ Licenses: pkg.NewLicenseSet(),
Name: "natord", Metadata: pkg.RustCargoLockEntry{
Version: "1.0.9", Name: "schannel",
Source: "registry+https://github.com/rust-lang/crates.io-index", Version: "0.1.26",
Checksum: "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c", Source: "registry+https://github.com/rust-lang/crates.io-index",
Dependencies: []string{}, Checksum: "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1",
}, Dependencies: []string{
}, "windows-sys 0.59.0",
{
Name: "nom",
Version: "4.2.3",
PURL: "pkg:cargo/nom@4.2.3",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "nom",
Version: "4.2.3",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6",
Dependencies: []string{
"memchr",
"version_check",
},
},
},
{
Name: "unicode-bidi",
Version: "0.3.4",
PURL: "pkg:cargo/unicode-bidi@0.3.4",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "unicode-bidi",
Version: "0.3.4",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5",
Dependencies: []string{
"matches",
},
},
},
{
Name: "version_check",
Version: "0.1.5",
PURL: "pkg:cargo/version_check@0.1.5",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "version_check",
Version: "0.1.5",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd",
Dependencies: []string{},
},
},
{
Name: "winapi",
Version: "0.3.9",
PURL: "pkg:cargo/winapi@0.3.9",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi",
Version: "0.3.9",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419",
Dependencies: []string{
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
},
},
},
{
Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0",
PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6",
Dependencies: []string{},
},
},
{
Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0",
PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f",
Dependencies: []string{},
}, },
}, },
} }
// TODO: no relationships are under test yet unicodeBidi := pkg.Package{
var expectedRelationships []artifact.Relationship Name: "unicode-bidi",
Version: "0.3.4",
PURL: "pkg:cargo/unicode-bidi@0.3.4",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "unicode-bidi",
Version: "0.3.4",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5",
Dependencies: []string{
"matches",
"bogus", // a bad dependency to test error handling
},
},
}
versionCheck := pkg.Package{
Name: "version_check",
Version: "0.1.5",
PURL: "pkg:cargo/version_check@0.1.5",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "version_check",
Version: "0.1.5",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd",
Dependencies: []string{},
},
}
winapi := pkg.Package{
Name: "winapi",
Version: "0.3.9",
PURL: "pkg:cargo/winapi@0.3.9",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi",
Version: "0.3.9",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419",
Dependencies: []string{
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
},
},
}
winAPIi686PCWindowsGNU := pkg.Package{
Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0",
PURL: "pkg:cargo/winapi-i686-pc-windows-gnu@0.4.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi-i686-pc-windows-gnu",
Version: "0.4.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6",
Dependencies: []string{},
},
}
winAPIx8664PCWindowsGNU := pkg.Package{
Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0",
PURL: "pkg:cargo/winapi-x86_64-pc-windows-gnu@0.4.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "winapi-x86_64-pc-windows-gnu",
Version: "0.4.0",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f",
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{},
},
}
windowsSys59 := pkg.Package{
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"