Skip to content

Update RustCli Parsing to process pkgId, and introduce manual override #1106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ private CargoComponent()
// reserved for deserialization
}

public CargoComponent(string name, string version, string author = null, string license = null)
public CargoComponent(string name, string version, string author = null, string license = null, string source = null)
{
this.Name = this.ValidateRequiredInput(name, nameof(this.Name), nameof(ComponentType.Cargo));
this.Version = this.ValidateRequiredInput(version, nameof(this.Version), nameof(ComponentType.Cargo));
this.Author = author;
this.License = license;
this.Source = source;
}

public string Name { get; set; }
Expand All @@ -28,6 +29,9 @@ public CargoComponent(string name, string version, string author = null, string

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? License { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? Source { get; set; }
#nullable disable

public override ComponentType Type => ComponentType.Cargo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.ComponentDetection.Detectors.Rust;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Rust.Contracts;
using Microsoft.Extensions.Logging;
using MoreLinq.Extensions;
using Newtonsoft.Json;
using Tomlyn;

Expand All @@ -21,8 +22,7 @@ namespace Microsoft.ComponentDetection.Detectors.Rust;
/// </summary>
public class RustCliDetector : FileComponentDetector
{
//// PkgName[ Version][ (Source)]
private static readonly Regex DependencyFormatRegex = new Regex(
private static readonly Regex DependencyFormatRegexCargoLock = new Regex(
@"^(?<packageName>[^ ]+)(?: (?<version>[^ ]+))?(?: \((?<source>[^()]*)\))?$",
RegexOptions.Compiled);

Expand All @@ -33,22 +33,27 @@ public class RustCliDetector : FileComponentDetector

private readonly ICommandLineInvocationService cliService;

private readonly IEnvironmentVariableService envVarService;

/// <summary>
/// Initializes a new instance of the <see cref="RustCliDetector"/> class.
/// </summary>
/// <param name="componentStreamEnumerableFactory">The component stream enumerable factory.</param>
/// <param name="walkerFactory">The walker factory.</param>
/// <param name="cliService">The command line invocation service.</param>
/// <param name="envVarService">The environment variable reader service.</param>
/// <param name="logger">The logger.</param>
public RustCliDetector(
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
IObservableDirectoryWalkerFactory walkerFactory,
ICommandLineInvocationService cliService,
IEnvironmentVariableService envVarService,
ILogger<RustCliDetector> logger)
{
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
this.Scanner = walkerFactory;
this.cliService = cliService;
this.envVarService = envVarService;
this.Logger = logger;
}

Expand All @@ -62,7 +67,7 @@ public RustCliDetector(
public override IEnumerable<ComponentType> SupportedComponentTypes => new[] { ComponentType.Cargo };

/// <inheritdoc />
public override int Version => 3;
public override int Version => 4;

/// <inheritdoc />
public override IList<string> SearchPatterns { get; } = new[] { "Cargo.toml" };
Expand All @@ -77,7 +82,14 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID

try
{
if (!await this.cliService.CanCommandBeLocatedAsync("cargo", null))
if (this.IsRustCliManuallyDisabled())
{
this.Logger.LogWarning("Rust Cli has been manually disabled, fallback strategy performed.");
record.DidRustCliCommandFail = false;
record.WasRustFallbackStrategyUsed = true;
record.FallbackReason = "Manually Disabled";
}
else if (!await this.cliService.CanCommandBeLocatedAsync("cargo", null))
{
this.Logger.LogWarning("Could not locate cargo command. Skipping Rust CLI detection");
record.DidRustCliCommandFail = true;
Expand Down Expand Up @@ -112,10 +124,13 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
var graph = BuildGraph(metadata);

var packages = metadata.Packages.ToDictionary(
x => $"{x.Name} {x.Version}",
x => (
x => $"{x.Id}",
x => new CargoComponent(
x.Name,
x.Version,
(x.Authors == null || x.Authors.Any(a => string.IsNullOrWhiteSpace(a)) || !x.Authors.Any()) ? null : string.Join(", ", x.Authors),
string.IsNullOrWhiteSpace(x.License) ? null : x.License));
string.IsNullOrWhiteSpace(x.License) ? null : x.License,
x.Source));

var root = metadata.Resolve.Root;
HashSet<string> visitedDependencies = new();
Expand Down Expand Up @@ -187,9 +202,9 @@ private static bool ShouldFallbackFromError(string error)
return true;
}

private static bool ParseDependency(string dependency, out string packageName, out string version, out string source)
private static bool ParseDependencyCargoLock(string dependency, out string packageName, out string version, out string source)
{
var match = DependencyFormatRegex.Match(dependency);
var match = DependencyFormatRegexCargoLock.Match(dependency);
var packageNameMatch = match.Groups["packageName"];
var versionMatch = match.Groups["version"];
var sourceMatch = match.Groups["source"];
Expand All @@ -206,41 +221,43 @@ private static bool ParseDependency(string dependency, out string packageName, o
return match.Success;
}

private bool IsRustCliManuallyDisabled()
{
return this.envVarService.IsEnvironmentVariableValueTrue("DisableRustCliScan");
}

private void TraverseAndRecordComponents(
ISingleFileComponentRecorder recorder,
string location,
IReadOnlyDictionary<string, Node> graph,
string id,
DetectedComponent parent,
Dep depInfo,
IReadOnlyDictionary<string, (string Authors, string License)> packagesMetadata,
IReadOnlyDictionary<string, CargoComponent> packagesMetadata,
ISet<string> visitedDependencies,
bool explicitlyReferencedDependency = false,
bool isTomlRoot = false)
{
try
{
var isDevelopmentDependency = depInfo?.DepKinds.Any(x => x.Kind is Kind.Dev) ?? false;
if (!ParseDependency(id, out var name, out var version, out var source))

if (!packagesMetadata.TryGetValue($"{id}", out var cargoComponent))
{
// Could not parse the dependency string
this.Logger.LogWarning("Failed to parse dependency '{Id}'", id);
this.Logger.LogWarning("Did not find dependency '{Id}' in Manifest.packages, skipping", id);
return;
}

var (authors, license) = packagesMetadata.TryGetValue($"{name} {version}", out var package)
? package
: (null, null);

var detectedComponent = new DetectedComponent(new CargoComponent(name, version, authors, license));
var detectedComponent = new DetectedComponent(cargoComponent);

if (!graph.TryGetValue(id, out var node))
{
this.Logger.LogWarning("Could not find {Id} at {Location} in cargo metadata output", id, location);
return;
}

var shouldRegister = !isTomlRoot && !source.StartsWith("path+file");
var shouldRegister = !isTomlRoot && cargoComponent.Source != null;
if (shouldRegister)
{
recorder.RegisterUsage(
Expand Down Expand Up @@ -298,7 +315,7 @@ private async Task ProcessCargoLockFallbackAsync(IComponentStream cargoTomlFile,
var cargoLockFileStream = this.FindCorrespondingCargoLock(cargoTomlFile, singleFileComponentRecorder);
if (cargoLockFileStream == null)
{
this.Logger.LogWarning("Could not find Cargo.lock file for {CargoTomlLocation}, skipping processing", cargoTomlFile.Location);
this.Logger.LogWarning("Fallback failed, could not find Cargo.lock file for {CargoTomlLocation}, skipping processing", cargoTomlFile.Location);
record.FallbackCargoLockFound = false;
return;
}
Expand Down Expand Up @@ -402,7 +419,7 @@ private void ProcessDependency(
try
{
// Extract the information from the dependency (name with optional version and source)
if (!ParseDependency(dependency, out var childName, out var childVersion, out var childSource))
if (!ParseDependencyCargoLock(dependency, out var childName, out var childVersion, out var childSource))
{
// Could not parse the dependency string
throw new FormatException($"Failed to parse dependency '{dependency}'");
Expand Down
Loading