From 99e910245b3cbf19caaac447e25688493362dea2 Mon Sep 17 00:00:00 2001 From: Justin Perez Date: Mon, 19 Feb 2024 12:35:33 -0800 Subject: [PATCH] feat(rust): allow specifying features --- docs/environment-variables.md | 11 ++++++-- .../rust/RustCliDetector.cs | 13 ++++++++-- .../RustCliDetectorTests.cs | 26 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 74f600e21..27491c4eb 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -5,7 +5,7 @@ Environment variables are sometimes used to control experimental features or adv ## `DisableGoCliScan` If the environment variable `DisableGoCliScan` is set to "true", we fall back to parsing `go.mod` and `go.sum` ourselves. -Otherwise, the Go detector uses go-cli command: `go list -m all` to discover Go dependencies. +Otherwise, the Go detector uses go-cli command: `go list -m all` to discover Go dependencies. [^1] ## `PyPiMaxCacheEntries` @@ -18,4 +18,11 @@ When set to any value, enables detector experiments, a feature to compare the re same ecosystem. The available experiments are found in the [`Experiments\Config`](../src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs) folder. -[1]: https://go.dev/ref/mod#go-mod-graph +## `CD_RUST_CLI_FEATURES` + +Specifies the features (comma seperated) to be passed into `cargo metadata`, which tells cargo to build the project with +the features for the workspace/project. By default, `cargo metadata` will run with `--all-features`, instructing `cargo` +to determine the build graph if all features should be built in the project/workspace. [^2] + +[^1]: https://go.dev/ref/mod#go-mod-graph +[^2]: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html#feature-selection diff --git a/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs b/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs index fca3f7c19..ee69050c3 100644 --- a/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/rust/RustCliDetector.cs @@ -26,12 +26,15 @@ public class RustCliDetector : FileComponentDetector, IExperimentalDetector @"^(?[^ ]+)(?: (?[^ ]+))?(?: \((?[^()]*)\))?$", RegexOptions.Compiled); + internal const string CustomFeaturesEnvironmentVariable = "CD_RUST_CLI_FEATURES"; + private static readonly TomlModelOptions TomlOptions = new TomlModelOptions { IgnoreMissingProperties = true, }; private readonly ICommandLineInvocationService cliService; + private readonly IEnvironmentVariableService environmentVariableService; /// /// Initializes a new instance of the class. @@ -39,16 +42,19 @@ public class RustCliDetector : FileComponentDetector, IExperimentalDetector /// The component stream enumerable factory. /// The walker factory. /// The command line invocation service. + /// The environment variable service. /// The logger. public RustCliDetector( IComponentStreamEnumerableFactory componentStreamEnumerableFactory, IObservableDirectoryWalkerFactory walkerFactory, ICommandLineInvocationService cliService, + IEnvironmentVariableService environmentVariableService, ILogger logger) { this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; this.Scanner = walkerFactory; this.cliService = cliService; + this.environmentVariableService = environmentVariableService; this.Logger = logger; } @@ -86,12 +92,15 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID } else { - // Use --all-features to ensure that even optional feature dependencies are detected. + // Use --all-features to ensure that even optional feature dependencies are detected if env var isnt set. + var specifiedFeatures = + this.environmentVariableService.GetEnvironmentVariable(CustomFeaturesEnvironmentVariable); + var cliResult = await this.cliService.ExecuteCommandAsync( "cargo", null, "metadata", - "--all-features", + string.IsNullOrEmpty(specifiedFeatures) ? "--all-features" : $"--features={specifiedFeatures}", "--manifest-path", componentStream.Location, "--format-version=1", diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs index 66c8f7d72..d1365a970 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/RustCliDetectorTests.cs @@ -21,6 +21,7 @@ public class RustCliDetectorTests : BaseDetectorTest { private Mock mockCliService; private Mock mockComponentStreamEnumerableFactory; + private Mock mockEnvVarService; [TestInitialize] public void InitCliMock() @@ -29,6 +30,8 @@ public void InitCliMock() this.DetectorTestUtility.AddServiceMock(this.mockCliService); this.mockComponentStreamEnumerableFactory = new Mock(); this.DetectorTestUtility.AddServiceMock(this.mockComponentStreamEnumerableFactory); + this.mockEnvVarService = new Mock(); + this.DetectorTestUtility.AddServiceMock(this.mockEnvVarService); } [TestMethod] @@ -1137,4 +1140,27 @@ public async Task RustCliDetector_FallBackLogicTriggeredOnFailedProcessingAsync( return; } + + [TestMethod] + public async Task RustCLiDetector_CustomFeaturesPassedIfPresentAsync() + { + this.mockCliService + .Setup(x => x.CanCommandBeLocatedAsync("cargo", It.IsAny>())) + .ReturnsAsync(true); + + var capturedCargoArgs = Enumerable.Empty(); + this.mockCliService.Setup(x => + x.ExecuteCommandAsync("cargo", It.IsAny>(), It.IsAny())) + .Callback((string _, IEnumerable _, string[] args) => capturedCargoArgs = args); + + this.mockEnvVarService + .Setup(x => x.GetEnvironmentVariable(RustCliDetector.CustomFeaturesEnvironmentVariable)) + .Returns("a,b"); + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("Cargo.toml", string.Empty) + .ExecuteDetectorAsync(); + + capturedCargoArgs.Should().Contain("--features=a,b"); + } }