Skip to content

Commit 0e5c9ee

Browse files
committed
feat: set target file for identity
1 parent 5642475 commit 0e5c9ee

File tree

29 files changed

+172
-156
lines changed

29 files changed

+172
-156
lines changed

pkg/ecosystems/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type GlobalOptions struct {
2525
AllowOutOfSync bool // Derived from --strict-out-of-sync (inverted); parsed in NewPluginOptionsFromRawFlags.
2626
ForceSingleGraph bool `arg:"--force-single-graph"`
2727
ForceIncludeWorkspacePackages bool `arg:"--internal-uv-workspace-packages"`
28+
ProjectName *string `arg:"--project-name"`
2829
RawFlags []string
2930
}
3031

pkg/ecosystems/orchestrator/orchestrator.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,35 @@ func depGraphOutputToSCAResult(output *parsers.DepGraphOutput, ictx workflow.Inv
229229

230230
if dg.PkgManager.Name != "" {
231231
result.ProjectDescriptor.Identity.Type = dg.PkgManager.Name
232-
// Extract runtime from depgraph if available
233-
result.ProjectDescriptor.Identity.TargetRuntime = &dg.PkgManager.Name
232+
result.ProjectDescriptor.Identity.TargetFile = getProjectTargetFileBasedOnType(dg.PkgManager.Name, output.NormalisedTargetFile)
234233
}
235234
}
236235

237236
return result, nil
238237
}
238+
239+
// getProjectTargetFileBasedOnType determines if a plugin sets targetFile based on package manager type.
240+
// Plugin audit: https://snyksec.atlassian.net/wiki/spaces/TOE1/pages/4651909131/Plugin+Audit
241+
func getProjectTargetFileBasedOnType(projectType, file string) *string {
242+
switch projectType {
243+
case "pip":
244+
// snyk-python-plugin sets targetFile for Pipfile and setup.py, but not for requirements.txt
245+
if strings.HasSuffix(file, "requirements.txt") {
246+
return nil
247+
}
248+
return &file
249+
case "gradle":
250+
// snyk-gradle-plugin sets targetFile for build.gradle.kts but not for build.gradle
251+
if strings.HasSuffix(file, "build.gradle.kts") {
252+
return &file
253+
}
254+
return nil
255+
case "poetry", "gomodules", "golangdep", "nuget", "paket", "composer", "cocoapods", "hex", "swift":
256+
// These plugins set relative path to provided targetFile
257+
return &file
258+
case "npm", "yarn", "pnpm", "maven", "sbt", "rubygems":
259+
// These plugins do not set plugin.targetFile
260+
return nil
261+
}
262+
return nil
263+
}

pkg/ecosystems/python/pip/depgraph.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,24 @@ func getNodeID(item *InstallItem) string {
6565
// ToDependencyGraph converts a pip install Report into a DepGraph using the dep-graph builder.
6666
// The root node represents the project and points to all direct dependencies.
6767
// The pkgManager parameter specifies the package manager name (e.g., PkgManagerPip, PkgManagerPipenv).
68-
func (r *Report) ToDependencyGraph(ctx context.Context, log logger.Logger, pkgManager PkgManagerName) (*depgraph.DepGraph, error) {
68+
// The projectName parameter sets the root package name (defaults to "root" if empty).
69+
func (r *Report) ToDependencyGraph(ctx context.Context, log logger.Logger, pkgManager PkgManagerName, projectName string) (*depgraph.DepGraph, error) {
6970
if r == nil {
7071
return nil, fmt.Errorf("report cannot be nil")
7172
}
7273

7374
numPackages := len(r.Install)
7475
log.Debug(ctx, "Converting pip report to dependency graph", logger.Attr("total_packages", numPackages))
7576

77+
// Use provided project name or default to "root"
78+
if projectName == "" {
79+
projectName = "root"
80+
}
81+
7682
// Create a builder with the specified package manager and a root package
7783
builder, err := depgraph.NewBuilder(
7884
&depgraph.PkgManager{Name: pkgManager.String()},
79-
&depgraph.PkgInfo{Name: "root", Version: "0.0.0"},
85+
&depgraph.PkgInfo{Name: projectName, Version: "0.0.0"},
8086
)
8187
if err != nil {
8288
return nil, fmt.Errorf("failed to create depgraph builder: %w", err)

pkg/ecosystems/python/pip/depgraph_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
7373
},
7474
}
7575

76-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
76+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
7777
require.NoError(t, err)
7878
require.NotNil(t, dg)
7979

@@ -137,7 +137,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
137137
},
138138
}
139139

140-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
140+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
141141
require.NoError(t, err)
142142
require.NotNil(t, dg)
143143

@@ -183,7 +183,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
183183
},
184184
}
185185

186-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
186+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
187187
require.NoError(t, err)
188188
require.NotNil(t, dg)
189189

@@ -199,7 +199,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
199199
Install: []InstallItem{},
200200
}
201201

202-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
202+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
203203
require.NoError(t, err)
204204
require.NotNil(t, dg)
205205

@@ -210,7 +210,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
210210

211211
t.Run("nil report", func(t *testing.T) {
212212
var report *Report
213-
_, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
213+
_, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
214214
assert.Error(t, err)
215215
assert.Contains(t, err.Error(), "cannot be nil")
216216
})
@@ -268,7 +268,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
268268
},
269269
}
270270

271-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
271+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
272272
require.NoError(t, err)
273273
require.NotNil(t, dg)
274274

@@ -330,7 +330,7 @@ func TestReport_ToDependencyGraph(t *testing.T) {
330330
},
331331
}
332332

333-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
333+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
334334
require.NoError(t, err)
335335
require.NotNil(t, dg)
336336

@@ -388,7 +388,7 @@ func TestToDependencyGraph_PruningBehavior(t *testing.T) {
388388
},
389389
}
390390

391-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
391+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
392392
require.NoError(t, err)
393393

394394
// Find pkg-a's deps
@@ -452,7 +452,7 @@ func TestToDependencyGraph_PruningBehavior(t *testing.T) {
452452
},
453453
}
454454

455-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
455+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
456456
require.NoError(t, err)
457457

458458
// Find pkg-a and pkg-b deps
@@ -515,7 +515,7 @@ func TestToDependencyGraph_PruningBehavior(t *testing.T) {
515515
},
516516
}
517517

518-
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip)
518+
dg, err := report.ToDependencyGraph(context.Background(), logger.Nop(), PkgManagerPip, "")
519519
require.NoError(t, err)
520520

521521
// Find pkg-c's deps - should only have ONE pkg-d, not duplicated

pkg/ecosystems/python/pip/plugin.go

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"os/exec"
8+
"path/filepath"
89
"strings"
910
"sync"
1011

@@ -65,7 +66,8 @@ func (p Plugin) BuildDepGraphsFromDir(
6566

6667
for _, file := range files {
6768
g.Go(func() error {
68-
result, err := p.buildDepGraphFromFile(ctx, log, file, pythonVersion, options.Python.NoBuildIsolation)
69+
projectName := GetProjectName(file.RelPath, dir, options)
70+
result, err := p.buildDepGraphFromFile(ctx, log, file, pythonVersion, options.Python.NoBuildIsolation, projectName)
6971
if err != nil {
7072
attrs := []logger.Field{
7173
logger.Attr(logFieldFile, file.RelPath),
@@ -82,8 +84,7 @@ func (p Plugin) BuildDepGraphsFromDir(
8284
result = ecosystems.SCAResult{
8385
ProjectDescriptor: identity.ProjectDescriptor{
8486
Identity: identity.ProjectIdentity{
85-
Type: "pip",
86-
TargetFile: &file.RelPath,
87+
Type: "pip",
8788
},
8889
},
8990
Error: err,
@@ -101,9 +102,9 @@ func (p Plugin) BuildDepGraphsFromDir(
101102
return nil, fmt.Errorf("error building dependency graphs: %w", err)
102103
}
103104

104-
processedFiles := make([]string, 0, len(results))
105-
for _, result := range results {
106-
processedFiles = append(processedFiles, result.ProjectDescriptor.GetTargetFile())
105+
processedFiles := make([]string, 0, len(files))
106+
for _, file := range files {
107+
processedFiles = append(processedFiles, file.RelPath)
107108
}
108109

109110
return &ecosystems.PluginResult{
@@ -143,17 +144,43 @@ func (p Plugin) discoverRequirementsFiles(ctx context.Context, dir string, optio
143144
return files, nil
144145
}
145146

147+
// GetProjectName determines the project name based on the file path and options.
148+
// If --project-name is set, it uses that. Otherwise, it uses the directory name
149+
// containing the file. For example:
150+
// - "project/test/requirements.txt" -> "test"
151+
// - "project/requirements.txt" -> "project"
152+
// - "requirements.txt" (with scanDir="/path/to/myproject") -> "myproject"
153+
func GetProjectName(filePath string, scanDir string, options *ecosystems.SCAPluginOptions) string {
154+
// If --project-name is explicitly set, use it
155+
if options.Global.ProjectName != nil && *options.Global.ProjectName != "" {
156+
return *options.Global.ProjectName
157+
}
158+
159+
// Extract the directory name from the file path
160+
dir := filepath.Dir(filePath)
161+
projectName := filepath.Base(dir)
162+
163+
// If we're at the root or the directory name is ".", use the scan directory name
164+
if projectName == "." || projectName == "/" || projectName == "" {
165+
return filepath.Base(scanDir)
166+
}
167+
168+
return projectName
169+
}
170+
146171
// buildDepGraphFromFile builds a dependency graph from a requirements.txt file.
147172
func (p Plugin) buildDepGraphFromFile(
148173
ctx context.Context,
149174
log logger.Logger,
150175
file discovery.FindResult,
151176
pythonVersion string,
152177
noBuildIsolation bool,
178+
projectName string,
153179
) (ecosystems.SCAResult, error) {
154180
log.Debug(ctx, "Building dependency graph",
155181
logger.Attr(logFieldFile, file.RelPath),
156-
logger.Attr("python_version", pythonVersion))
182+
logger.Attr("python_version", pythonVersion),
183+
logger.Attr("project_name", projectName))
157184

158185
// Get pip install report (dry-run, no actual installation)
159186
report, err := GetInstallReport(ctx, log, file.Path, noBuildIsolation)
@@ -162,7 +189,7 @@ func (p Plugin) buildDepGraphFromFile(
162189
}
163190

164191
// Convert report to dependency graph
165-
depGraph, err := report.ToDependencyGraph(ctx, log, PkgManagerPip)
192+
depGraph, err := report.ToDependencyGraph(ctx, log, PkgManagerPip, projectName)
166193
if err != nil {
167194
return ecosystems.SCAResult{}, fmt.Errorf("failed to convert pip report to dependency graph for %s: %w", file.RelPath, err)
168195
}
@@ -174,8 +201,7 @@ func (p Plugin) buildDepGraphFromFile(
174201
DepGraph: depGraph,
175202
ProjectDescriptor: identity.ProjectDescriptor{
176203
Identity: identity.ProjectIdentity{
177-
Type: "pip",
178-
TargetFile: &file.RelPath,
204+
Type: "pip",
179205
},
180206
},
181207
}, nil

pkg/ecosystems/python/pip/plugin_integration_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ func TestPlugin_BuildDepGraphsFromDir_PipErrors(t *testing.T) {
157157
pipResult := result.Results[0]
158158

159159
// Basic metadata expectations
160-
assert.Equal(t, "requirements.txt", pipResult.ProjectDescriptor.GetTargetFile())
161160
if pythonVersion != "" && pipResult.ProjectDescriptor.Identity.TargetRuntime != nil {
162161
assert.Contains(t, *pipResult.ProjectDescriptor.Identity.TargetRuntime, fmt.Sprintf("python@%s", pythonVersion))
163162
}
@@ -234,13 +233,13 @@ func assertResultsMatchExpected(t *testing.T, actual, expected []ecosystems.SCAR
234233
sortActual := make([]ecosystems.SCAResult, len(actual))
235234
copy(sortActual, actual)
236235
sort.Slice(sortActual, func(i, j int) bool {
237-
return sortActual[i].ProjectDescriptor.GetTargetFile() < sortActual[j].ProjectDescriptor.GetTargetFile()
236+
return sortActual[i].DepGraph.GetRootPkg().Info.Name < sortActual[j].DepGraph.GetRootPkg().Info.Name
238237
})
239238

240239
sortExpected := make([]ecosystems.SCAResult, len(expected))
241240
copy(sortExpected, expected)
242241
sort.Slice(sortExpected, func(i, j int) bool {
243-
return sortExpected[i].ProjectDescriptor.GetTargetFile() < sortExpected[j].ProjectDescriptor.GetTargetFile()
242+
return sortExpected[i].DepGraph.GetRootPkg().Info.Name < sortExpected[j].DepGraph.GetRootPkg().Info.Name
244243
})
245244

246245
// Sync runtime field (varies by Python version) and clear Error field (not in JSON)

pkg/ecosystems/python/pipenv/plugin.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ func (p Plugin) BuildDepGraphsFromDir(
6868

6969
for _, file := range files {
7070
g.Go(func() error {
71-
result, err := p.buildDepGraphFromPipfile(ctx, log, file, pythonVersion, options.Python.NoBuildIsolation, options.Global.IncludeDev)
71+
projectName := pip.GetProjectName(file.RelPath, dir, options)
72+
result, err := p.buildDepGraphFromPipfile(ctx, log, file, pythonVersion, options.Python.NoBuildIsolation, options.Global.IncludeDev, projectName)
7273
if err != nil {
7374
attrs := []logger.Field{
7475
logger.Attr(logFieldFile, file.RelPath),
@@ -85,7 +86,7 @@ func (p Plugin) BuildDepGraphsFromDir(
8586
result = ecosystems.SCAResult{
8687
ProjectDescriptor: identity.ProjectDescriptor{
8788
Identity: identity.ProjectIdentity{
88-
Type: "pipenv",
89+
Type: "pip",
8990
TargetFile: &file.RelPath,
9091
},
9192
},
@@ -153,10 +154,12 @@ func (p Plugin) buildDepGraphFromPipfile(
153154
pythonVersion string,
154155
noBuildIsolation bool,
155156
includeDevDeps bool,
157+
projectName string,
156158
) (ecosystems.SCAResult, error) {
157159
log.Debug(ctx, "Building dependency graph from Pipfile",
158160
logger.Attr(logFieldFile, file.RelPath),
159-
logger.Attr("python_version", pythonVersion))
161+
logger.Attr("python_version", pythonVersion),
162+
logger.Attr("project_name", projectName))
160163

161164
// Parse Pipfile
162165
pipfile, err := ParsePipfile(file.Path)
@@ -191,7 +194,7 @@ func (p Plugin) buildDepGraphFromPipfile(
191194
}
192195

193196
// Convert report to dependency graph
194-
depGraph, err := report.ToDependencyGraph(ctx, log, pip.PkgManagerPipenv)
197+
depGraph, err := report.ToDependencyGraph(ctx, log, pip.PkgManagerPipenv, projectName)
195198
if err != nil {
196199
return ecosystems.SCAResult{}, fmt.Errorf("failed to convert pip report to dependency graph: %w", err)
197200
}
@@ -203,7 +206,7 @@ func (p Plugin) buildDepGraphFromPipfile(
203206
DepGraph: depGraph,
204207
ProjectDescriptor: identity.ProjectDescriptor{
205208
Identity: identity.ProjectIdentity{
206-
Type: "pipenv",
209+
Type: "pip",
207210
TargetFile: &file.RelPath,
208211
},
209212
},

pkg/ecosystems/testdata/fixtures/python/empty/expected_plugin.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
},
88
"pkgs": [
99
{
10-
"id": "root@0.0.0",
10+
"id": "empty@0.0.0",
1111
"info": {
12-
"name": "root",
12+
"name": "empty",
1313
"version": "0.0.0"
1414
}
1515
}
@@ -19,15 +19,14 @@
1919
"nodes": [
2020
{
2121
"nodeId": "root-node",
22-
"pkgId": "root@0.0.0",
22+
"pkgId": "empty@0.0.0",
2323
"deps": []
2424
}
2525
]
2626
}
2727
},
2828
"projectDescriptor": {
2929
"identity": {
30-
"targetFile": "requirements.txt",
3130
"targetRuntime": "python@3.14.1"
3231
}
3332
}

0 commit comments

Comments
 (0)