Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
* text=auto eol=lf
pkg/dependency/parser/dotnet/core_deps/testdata/multi-project/Web.deps.json linguist-generated=true
pkg/dependency/parser/dotnet/core_deps/testdata/multi-project/**/*.cs linguist-generated=true
pkg/dependency/parser/dotnet/core_deps/testdata/multi-project/**/*.csproj linguist-generated=true
pkg/dependency/parser/dotnet/core_deps/testdata/multi-project/**/*.sln linguist-generated=true
44 changes: 35 additions & 9 deletions pkg/dependency/parser/dotnet/core_deps/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"context"
"maps"
"slices"
"sort"
"strings"
Expand Down Expand Up @@ -101,10 +102,11 @@
// collectPackages builds the package map from the `libraries` section, returning the
// packages keyed by ID and the ID of the root project (empty if none was found).
func (p *Parser) collectPackages(depsFile dotNetDependencies, targetLibs map[string]TargetLib, targetLibsFound bool) (map[string]ftypes.Package, string) {
var projectNameVer string
pkgs := make(map[string]ftypes.Package, len(depsFile.Libraries))
var projects []string

for nameVer, lib := range depsFile.Libraries {
for _, nameVer := range slices.Sorted(maps.Keys(depsFile.Libraries)) {
lib := depsFile.Libraries[nameVer]
name, version, ok := strings.Cut(nameVer, "/")
if !ok {
// Invalid name
Expand Down Expand Up @@ -137,22 +139,46 @@
Locations: []ftypes.Location{ftypes.Location(lib.Location)},
}

// Identify root package
if strings.EqualFold(lib.Type, "project") {
if projectNameVer != "" {
p.logger.Warn("Multiple root projects found in .deps.json", log.String("existing_root", projectNameVer), log.String("new_root", id))
continue
}
projectNameVer = id
pkg.Relationship = ftypes.RelationshipRoot
projects = append(projects, id)
}

pkgs[pkg.ID] = pkg
}

projectNameVer := rootProject(projects, targetLibs)
if projectNameVer != "" {
pkg := pkgs[projectNameVer]
pkg.Relationship = ftypes.RelationshipRoot
pkgs[projectNameVer] = pkg
}

return pkgs, projectNameVer
}

func rootProject(projects []string, targetLibs map[string]TargetLib) string {
referenced := make(map[string]struct{})

Check failure on line 160 in pkg/dependency/parser/dotnet/core_deps/parse.go

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

ruleguard: use github.com/aquasecurity/trivy/pkg/set.Set instead of map. (gocritic)
Comment thread
jetersen marked this conversation as resolved.
Outdated
for _, lib := range targetLibs {
for name, version := range lib.Dependencies {
referenced[packageID(name, version)] = struct{}{}
}
}

var roots []string
for _, project := range projects {
if _, ok := referenced[project]; !ok {
roots = append(roots, project)
}
}
if len(roots) == 1 {
return roots[0]
}
if len(projects) > 0 {
Comment thread
jetersen marked this conversation as resolved.
Outdated
return projects[0]
Comment thread
jetersen marked this conversation as resolved.
Outdated
}
return ""
}

// buildDependencyGraph fills the Relationship field of each package and builds the
// dependency graph from the `targets` section.
func (p *Parser) buildDependencyGraph(pkgs map[string]ftypes.Package, targetLibs map[string]TargetLib, directDeps []string) ftypes.Dependencies {
Expand Down
57 changes: 57 additions & 0 deletions pkg/dependency/parser/dotnet/core_deps/parse_test.go
Comment thread
jetersen marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,60 @@ func TestParse(t *testing.T) {
})
}
}

func TestParseMultiProject(t *testing.T) {
f, err := os.Open("testdata/multi-project/Web.deps.json")
require.NoError(t, err)

got, gotDeps, err := NewParser().Parse(t.Context(), f)
require.NoError(t, err)

assert.Equal(t, []ftypes.Package{
{
ID: "Web/1.0.0",
Name: "Web",
Version: "1.0.0",
Relationship: ftypes.RelationshipRoot,
Locations: []ftypes.Location{
{StartLine: 48, EndLine: 52},
},
},
{
ID: "Api/1.0.0",
Name: "Api",
Version: "1.0.0",
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{StartLine: 60, EndLine: 64},
},
},
{
ID: "Newtonsoft.Json/13.0.3",
Name: "Newtonsoft.Json",
Version: "13.0.3",
Relationship: ftypes.RelationshipDirect,
Locations: []ftypes.Location{
{StartLine: 53, EndLine: 59},
},
},
{
ID: "Data/1.0.0",
Name: "Data",
Version: "1.0.0",
Relationship: ftypes.RelationshipIndirect,
Locations: []ftypes.Location{
{StartLine: 65, EndLine: 69},
},
},
}, got)
assert.Equal(t, []ftypes.Dependency{
{
ID: "Api/1.0.0",
DependsOn: []string{"Data/1.0.0"},
},
{
ID: "Web/1.0.0",
DependsOn: []string{"Api/1.0.0", "Newtonsoft.Json/13.0.3"},
},
}, gotDeps)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Data/Data.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Api;

public static class Message
{
public static string Get() => Data.Message.Get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Data;

public static class Message
{
public static string Get() => "Hello from Data";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{C49BE224-A508-462A-9FF1-542C62927F9E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "Api\Api.csproj", "{C813BF86-F9BC-4D86-B065-F9E74DB04940}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|x64.ActiveCfg = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|x64.Build.0 = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|x86.ActiveCfg = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Debug|x86.Build.0 = Debug|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|Any CPU.Build.0 = Release|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|x64.ActiveCfg = Release|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|x64.Build.0 = Release|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|x86.ActiveCfg = Release|Any CPU
{C49BE224-A508-462A-9FF1-542C62927F9E}.Release|x86.Build.0 = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|x64.ActiveCfg = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|x64.Build.0 = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|x86.ActiveCfg = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Debug|x86.Build.0 = Debug|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|Any CPU.Build.0 = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|x64.ActiveCfg = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|x64.Build.0 = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|x86.ActiveCfg = Release|Any CPU
{C813BF86-F9BC-4D86-B065-F9E74DB04940}.Release|x86.Build.0 = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|x64.ActiveCfg = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|x64.Build.0 = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|x86.ActiveCfg = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Debug|x86.Build.0 = Debug|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|Any CPU.Build.0 = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|x64.ActiveCfg = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|x64.Build.0 = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|x86.ActiveCfg = Release|Any CPU
{7AB597BA-7C1D-4BA0-8FFF-A9A280AAC3B1}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Multi-project fixture

This fixture contains a real multi-project solution for verifying a published
application with project references:

```text
Web -> Api -> Data
```

`Web` also references `Newtonsoft.Json` directly. To regenerate `Web.deps.json`
with the .NET 10 SDK, run from this directory:

```shell
(
set -euo pipefail
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT

cp -R MultiProject.sln Web Api Data "$tmp_dir"
dotnet publish "$tmp_dir/Web/Web.csproj" \
--output "$tmp_dir/publish" \
-m:1 \
-p:UseSharedCompilation=false
cp "$tmp_dir/publish/Web.deps.json" Web.deps.json
)
```

The command builds in a temporary directory so it does not leave `bin` or `obj`
directories in the fixture.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"Web/1.0.0": {
"dependencies": {
"Api": "1.0.0",
"Newtonsoft.Json": "13.0.3"
},
"runtime": {
"Web.dll": {}
}
},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
},
"Api/1.0.0": {
"dependencies": {
"Data": "1.0.0"
},
"runtime": {
"Api.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"Data/1.0.0": {
"runtime": {
"Data.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"Web/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
},
"Api/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Data/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
System.Console.WriteLine(Api.Message.Get());
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Api/Api.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
Loading