-
Notifications
You must be signed in to change notification settings - Fork 94
/
Copy pathMvnCliComponentDetector.cs
161 lines (135 loc) · 7.06 KB
/
MvnCliComponentDetector.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
namespace Microsoft.ComponentDetection.Detectors.Maven;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Microsoft.ComponentDetection.Common;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.Extensions.Logging;
public class MvnCliComponentDetector : FileComponentDetector
{
private readonly IMavenCommandService mavenCommandService;
public MvnCliComponentDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
IMavenCommandService mavenCommandService,
ILogger<MvnCliComponentDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.mavenCommandService = mavenCommandService;
this.Logger = logger;
}
public override string Id => "MvnCli";
public override IList<string> SearchPatterns => new List<string> { "pom.xml" };
public override IEnumerable<ComponentType> SupportedComponentTypes => new[] { ComponentType.Maven };
public override int Version => 2;
public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Maven) };
protected override async Task<IObservable<ProcessRequest>> OnPrepareDetectionAsync(IObservable<ProcessRequest> processRequests, IDictionary<string, string> detectorArgs)
{
if (!await this.mavenCommandService.MavenCLIExistsAsync())
{
this.Logger.LogDebug("Skipping maven detection as maven is not available in the local PATH.");
return Enumerable.Empty<ProcessRequest>().ToObservable();
}
var processPomFile = new ActionBlock<ProcessRequest>(this.mavenCommandService.GenerateDependenciesFileAsync);
await this.RemoveNestedPomXmls(processRequests).ForEachAsync(processRequest =>
{
processPomFile.Post(processRequest);
});
processPomFile.Complete();
await processPomFile.Completion;
return this.ComponentStreamEnumerableFactory.GetComponentStreams(this.CurrentScanRequest.SourceDirectory, new[] { this.mavenCommandService.BcdeMvnDependencyFileName }, this.CurrentScanRequest.DirectoryExclusionPredicate)
.Select(componentStream =>
{
// The file stream is going to be disposed after the iteration is finished
// so is necessary to read the content and keep it in memory, for further processing.
using var reader = new StreamReader(componentStream.Stream);
var content = reader.ReadToEnd();
return new ProcessRequest
{
ComponentStream = new ComponentStream
{
Stream = new MemoryStream(Encoding.UTF8.GetBytes(content)),
Location = componentStream.Location,
Pattern = componentStream.Pattern,
},
SingleFileComponentRecorder = this.ComponentRecorder.CreateSingleFileComponentRecorder(
Path.Combine(Path.GetDirectoryName(componentStream.Location), "pom.xml")),
};
})
.ToObservable();
}
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs)
{
this.mavenCommandService.ParseDependenciesFile(processRequest);
File.Delete(processRequest.ComponentStream.Location);
await Task.CompletedTask;
}
private IObservable<ProcessRequest> RemoveNestedPomXmls(IObservable<ProcessRequest> componentStreams)
{
var directoryItemFacades = new List<DirectoryItemFacade>();
var directoryItemFacadesByPath = new Dictionary<string, DirectoryItemFacade>();
return Observable.Create<ProcessRequest>(s =>
{
return componentStreams.Subscribe(
processRequest =>
{
var item = processRequest.ComponentStream;
var currentDir = item.Location;
DirectoryItemFacade last = null;
do
{
currentDir = Path.GetDirectoryName(currentDir);
// We've reached the top / root
if (currentDir == null)
{
// If our last directory isn't in our list of top level nodes, it should be added. This happens for the first processed item and then subsequent times we have a new root (edge cases with multiple hard drives, for example)
if (!directoryItemFacades.Contains(last))
{
directoryItemFacades.Add(last);
}
// If we got to the top without finding a directory that had a pom.xml on the way, we yield.
s.OnNext(processRequest);
break;
}
var directoryExisted = directoryItemFacadesByPath.TryGetValue(currentDir, out var current);
if (!directoryExisted)
{
directoryItemFacadesByPath[currentDir] = current = new DirectoryItemFacade
{
Name = currentDir,
Files = new List<IComponentStream>(),
Directories = new List<DirectoryItemFacade>(),
};
}
// If we came from a directory, we add it to our graph.
if (last != null)
{
current.Directories.Add(last);
}
// If we didn't come from a directory, it's because we're just getting started. Our current directory should include the file that led to it showing up in the graph.
else
{
current.Files.Add(item);
}
if (last != null && current.Files.FirstOrDefault(x => string.Equals(Path.GetFileName(x.Location), "pom.xml", StringComparison.OrdinalIgnoreCase)) != null)
{
this.Logger.LogDebug("Ignoring pom.xml at {ChildPomXmlLocation}, as it has a parent pom.xml that will be processed at {ParentDirName}\\pom.xml .", item.Location, current.Name);
break;
}
last = current;
}
// Go all the way up
while (currentDir != null);
},
s.OnCompleted);
});
}
}