-
Notifications
You must be signed in to change notification settings - Fork 115
Maven combined detector experiment #1628
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
base: main
Are you sure you want to change the base?
Maven combined detector experiment #1628
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1628 +/- ##
=======================================
+ Coverage 90.4% 90.7% +0.2%
=======================================
Files 441 444 +3
Lines 38314 39685 +1371
Branches 2347 2414 +67
=======================================
+ Hits 34674 36006 +1332
- Misses 3159 3187 +28
- Partials 481 492 +11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces MavenWithFallbackDetector, an experimental Maven detector that provides resilient dependency detection by combining Maven CLI execution with static pom.xml parsing fallback. The detector automatically falls back to static parsing when Maven CLI fails (e.g., due to authentication errors or missing CLI), ensuring components are still detected even in challenging build environments.
Changes:
- Added
MavenWithFallbackDetectorwith dual detection strategy (Maven CLI primary, static parsing fallback) and comprehensive telemetry - Registered new experiment configuration comparing the new detector against the standard
MvnCliComponentDetector - Added 13 comprehensive unit tests covering CLI scenarios, static parser edge cases, environment variable control, and nested pom.xml handling
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs |
Main detector implementation with Maven CLI detection, static XML parsing fallback, nested pom filtering, and telemetry tracking |
src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/MavenWithFallbackExperiment.cs |
Experiment configuration to compare new detector against existing MvnCliComponentDetector |
src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs |
Registered new detector and experiment in DI container |
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs |
Comprehensive test suite with 13 tests covering all detection scenarios and edge cases |
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
|
👋 Hi! It looks like you modified some files in the
If none of the above scenarios apply, feel free to ignore this comment 🙂 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
...Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/MavenWithFallbackExperiment.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
...Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/MavenWithFallbackExperiment.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
| "Could not authenticate", | ||
| "Access denied", | ||
| "Forbidden", | ||
| "status code: 401", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't this already covered by line 106 and 107?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trimmed it to
private static readonly string[] AuthErrorPatterns =
[
"401",
"403",
"Unauthorized",
"Access denied",
];
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
…tps://github.com/microsoft/component-detection into users/zhentan/maven-combined-detector-experiment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenCommandService.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
| services.AddSingleton<IMavenCommandService, MavenCommandService>(); | ||
| services.AddSingleton<IMavenStyleDependencyGraphParserService, MavenStyleDependencyGraphParserService>(); | ||
| services.AddSingleton<IComponentDetector, MvnCliComponentDetector>(); | ||
| services.AddSingleton<IComponentDetector, MavenWithFallbackDetector>(); |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Registering MavenWithFallbackDetector alongside MvnCliComponentDetector means both detectors will run concurrently during a normal scan (experimental detectors still execute even when their results are discarded). Since both detectors operate on pom.xml and generate/delete the same bcde.mvndeps output, this can create file races and corrupt the control detector’s results even when experiments are disabled. Consider gating registration behind the experiment flag, or ensure the experimental detector uses a distinct output filename/local repo path so it cannot interfere with MvnCliComponentDetector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the semaphor implementation, this should be ok. The experiment config will also be in CD internal repository.
| var pomFile = processRequest.ComponentStream; | ||
| var pomDir = Path.GetDirectoryName(pomFile.Location); | ||
| var depsFileName = this.mavenCommandService.BcdeMvnDependencyFileName; | ||
| var depsFilePath = Path.Combine(pomDir, depsFileName); | ||
|
|
||
| // Generate dependency file using Maven CLI. | ||
| // Note: If both MvnCliComponentDetector and this detector are enabled, | ||
| // they may run Maven CLI on the same pom.xml independently. | ||
| var result = await this.mavenCommandService.GenerateDependenciesFileAsync( | ||
| processRequest, | ||
| depsFileName, | ||
| cancellationToken); | ||
|
|
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depsFileName is set to BcdeMvnDependencyFileName (currently bcde.mvndeps) and later ProcessMvnCliResult deletes processRequest.ComponentStream.Location. If MvnCliComponentDetector is also running (it is registered), the two detectors will overwrite/delete each other’s deps files and break detection. Use a detector-specific output filename (and ideally a detector-specific local repository path via the new overload) and only delete files that this detector uniquely owns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the semaphor implementation, this should be ok. The experiment config will also be in CD internal repository.
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
| // Check if another detector already generated the deps file for this location | ||
| if (this.completedLocations.TryGetValue(pomFile.Location, out var cachedResult)) | ||
| { | ||
| this.logger.LogDebug("{DetectorPrefix}: Skipping duplicate \"dependency:tree\" for {PomFileLocation}, already generated", DetectorLogPrefix, pomFile.Location); | ||
| return cachedResult; | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The completedLocations cache is keyed only by pomFile.Location and the cached result is returned without verifying the side effect (that outputFileName was generated and still exists). With parallel detectors deleting bcde.mvndeps, a cached “success” can be returned while the file is already gone, causing downstream callers to incorrectly fall back.
Include outputFileName in the cache key and/or cache the deps file content (or validate file existence before returning cached success).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added file existence check to ensures that if a previous consumer deleted bcde.mvndeps, the next caller will re-run Maven CLI instead of getting a stale "success" result.
|
|
||
| var result = await this.GenerateDependenciesFileCoreAsync(processRequest, outputFileName, cancellationToken); | ||
| this.completedLocations.TryAdd(pomFile.Location, result); | ||
| return result; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
completedLocations is described as tracking locations that completed successfully, but the implementation caches all results (including failures) and never evicts entries. Since MavenCommandService is registered as a singleton, this can prevent retries of transient failures in later scans within the same process and can grow memory usage over time.
Only cache successful results and/or add an eviction/clear strategy scoped to a scan run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to only cache successful result
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/IMavenCommandService.cs
Outdated
Show resolved
Hide resolved
| /// <summary> | ||
| /// Per-location semaphores to prevent concurrent Maven CLI executions for the same pom.xml. | ||
| /// This allows multiple detectors (e.g., MvnCliComponentDetector and MavenWithFallbackDetector) | ||
| /// to safely share the same output file without race conditions. | ||
| /// </summary> | ||
| private readonly ConcurrentDictionary<string, SemaphoreSlim> locationLocks = new(); | ||
|
|
||
| /// <summary> | ||
| /// Tracks locations where dependency generation has completed successfully. | ||
| /// Used to skip duplicate executions when multiple detectors process the same pom.xml. | ||
| /// </summary> | ||
| private readonly ConcurrentDictionary<string, MavenCliResult> completedLocations = new(); |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The per-location locationLocks and completedLocations dictionaries grow monotonically and are never pruned, and the SemaphoreSlim instances are never disposed. Since MavenCommandService is registered as a singleton, repeated scans (or very large repos) can cause unbounded memory growth.
Consider using a bounded/expiring cache and removing/disposing per-location semaphores once generation is complete (or switching to a keyed lock implementation that doesn't retain entries indefinitely).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a clean up method in OnDetectionFinishedAsync to clear cache
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Show resolved
Hide resolved
| // Try to delete the deps file | ||
| try | ||
| { | ||
| File.Delete(processRequest.ComponentStream.Location); | ||
| } | ||
| catch | ||
| { | ||
| // Ignore deletion errors | ||
| } | ||
|
|
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessMvnCliResult deletes the generated deps file from disk. If MvnCliComponentDetector is also enabled, it enumerates bcde.mvndeps files only after all CLI runs complete; deleting the file here can make the other detector miss it (especially with the new MavenCommandService “skip duplicate” cache). If these detectors can run together, consider not deleting the file here (let a single owner delete), or ensure the two detectors are mutually exclusive via restrictions/experiments.
| // Try to delete the deps file | |
| try | |
| { | |
| File.Delete(processRequest.ComponentStream.Location); | |
| } | |
| catch | |
| { | |
| // Ignore deletion errors | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File.Delete will not throw on non existent file, so this should be ok
| return componentStreams | ||
| .ToList() | ||
| .SelectMany(allRequests => | ||
| { |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoveNestedPomXmls buffers the entire pom.xml request stream via ToList(), which can add significant memory pressure on repos with many pom.xml files. MvnCliComponentDetector’s RemoveNestedPomXmls implementation streams and avoids materializing everything. Consider reusing the existing streaming approach (or otherwise avoiding ToList()) so OnPrepareDetectionAsync scales better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@FernandoRojo Would this be an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
src/Microsoft.ComponentDetection.Detectors/maven/MvnCliComponentDetector.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenCommandService.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenWithFallbackDetectorTests.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenCommandServiceTests.cs
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenCommandServiceTests.cs
Outdated
Show resolved
Hide resolved
test/Microsoft.ComponentDetection.Detectors.Tests/MavenCommandServiceTests.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.ComponentDetection.Detectors/maven/MavenWithFallbackDetector.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/Microsoft.ComponentDetection.Detectors/maven/MavenCommandService.cs:120
GenerateDependenciesFileCoreAsynccreates a linkedCancellationTokenSource(cliFileTimeout) but never disposes it. This can leak registrations/timers over many invocations. Wrap it in ausing(ortry/finallywithDispose) so it’s always cleaned up.
var cliFileTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var timeoutSeconds = -1;
if (this.envVarService.DoesEnvironmentVariableExist(MvnCLIFileLevelTimeoutSecondsEnvVar)
&& int.TryParse(this.envVarService.GetEnvironmentVariable(MvnCLIFileLevelTimeoutSecondsEnvVar), out timeoutSeconds)
&& timeoutSeconds >= 0)
{
cliFileTimeout.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
this.logger.LogInformation("{DetectorPrefix}: {TimeoutVar} var was set to {TimeoutSeconds} seconds.", DetectorLogPrefix, MvnCLIFileLevelTimeoutSecondsEnvVar, timeoutSeconds);
}
| @@ -43,7 +57,57 @@ public async Task<bool> MavenCLIExistsAsync() | |||
| return await this.commandLineInvocationService.CanCommandBeLocatedAsync(PrimaryCommand, AdditionalValidCommands, MvnVersionArgument); | |||
| } | |||
|
|
|||
| public async Task GenerateDependenciesFileAsync(ProcessRequest processRequest, CancellationToken cancellationToken = default) | |||
| public async Task<MavenCliResult> GenerateDependenciesFileAsync(ProcessRequest processRequest, CancellationToken cancellationToken = default) | |||
| { | |||
| var pomFile = processRequest.ComponentStream; | |||
| var pomDir = Path.GetDirectoryName(pomFile.Location); | |||
| var depsFilePath = Path.Combine(pomDir, this.BcdeMvnDependencyFileName); | |||
|
|
|||
| // Check the cache before acquiring the semaphore to allow fast-path returns | |||
| // even when cancellation has been requested. | |||
| if (this.completedLocations.TryGetValue(pomFile.Location, out var cachedResult) | |||
| && cachedResult.Success | |||
| && File.Exists(depsFilePath)) | |||
| { | |||
| this.logger.LogDebug("{DetectorPrefix}: Skipping duplicate \"dependency:tree\" for {PomFileLocation}, already generated", DetectorLogPrefix, pomFile.Location); | |||
| return cachedResult; | |||
| } | |||
|
|
|||
| // Use semaphore to prevent concurrent Maven CLI executions for the same pom.xml. | |||
| // This allows MvnCliComponentDetector and MavenWithFallbackDetector to safely share the output file. | |||
| var semaphore = this.locationLocks.GetOrAdd(pomFile.Location, _ => new SemaphoreSlim(1, 1)); | |||
|
|
|||
| await semaphore.WaitAsync(cancellationToken); | |||
| try | |||
| { | |||
| // Re-check the cache after acquiring the semaphore in case another caller | |||
| // completed while we were waiting. | |||
| if (this.completedLocations.TryGetValue(pomFile.Location, out cachedResult) | |||
| && cachedResult.Success | |||
| && File.Exists(depsFilePath)) | |||
| { | |||
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The locationLocks/completedLocations dictionaries are keyed by pomFile.Location using the default (case-sensitive) comparer. On Windows/macOS this can lead to duplicate locks/cache entries for the same file path with different casing, causing extra CLI executions and unbounded growth. Consider normalizing to a full path and using StringComparer.OrdinalIgnoreCase for these dictionaries.
| public class MavenWithFallbackDetector : FileComponentDetector, IExperimentalDetector | ||
| { |
Copilot
AI
Feb 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MavenWithFallbackDetector implements IExperimentalDetector, which the orchestrator still executes by default (results are just discarded unless explicitly enabled). Because this detector runs Maven CLI and touches shared files, running it by default can introduce side effects even when the experiment isn’t enabled. Consider also implementing IDefaultOffComponentDetector (and enabling it only via experiment/DetectorArgs) to avoid unintended execution and side effects.
MavenWithFallbackDetector
Overview
The
MavenWithFallbackDetectoris an experimental Maven detector that combines Maven CLI detection with static pom.xml parsing fallback. It provides resilient Maven dependency detection even when the Maven CLI fails (e.g., due to authentication errors with private repositories).Key Features
mvn dependency:tree) for full transitive dependency resolutionDetection Flow
Core Components
1. Environment Variable Control
CD_MAVEN_DISABLE_CLI:
trueto bypass Maven CLI entirely and use only static parsingUseful when:
2. Internal Timeout Protection
The detector has an internal 5-minute timeout (
PrepareDetectionTimeout) as a safety guardrail:Why? The
OnPrepareDetectionAsyncphase doesn't have the same guardrails asOnFileFoundAsync. This timeout prevents hangs in the experimental detector. If the timeout is reached:TimedOut = true)3. Cancellation Token Propagation
The detector properly propagates cancellation tokens through the entire call chain, combining the framework's cancellation token with the internal timeout:
Key points:
Process.Kill()MvnCLIFileLevelTimeoutSecondsenvironment variable4. Sequential Maven CLI Execution
Maven CLI invocations are processed sequentially using
ActionBlockwith default settings:Why sequential execution?
mvnprocesses access~/.m2/repositorysimultaneouslyMvnCliComponentDetectorfor consistent performance5. RemoveNestedPomXmls
Filters pom.xml files to keep only root-level ones for Maven CLI processing.
Why? In multi-module Maven projects:
Running
mvn dependency:treeon the root pom automatically processes all child modules. Processing each nested pom separately would be redundant and slow.Algorithm:
6. Maven CLI Execution
Uses
IMavenCommandService.GenerateDependenciesFileAsync()to run:This generates a
bcde.mvndepsfile containing the full dependency tree.Immediate file reading: Unlike
MvnCliComponentDetectorwhich scans for deps files after all CLI executions, this detector reads the deps file content immediately after each successful CLI execution. This enables per-file success/failure tracking.7. Failure Detection
After each Maven CLI execution, the detector checks the result:
8. Static Parsing Fallback with Directory Optimization
When Maven CLI fails for a directory, the detector scans for all pom.xml files in that directory and its subdirectories:
Performance optimization: Directory paths are normalized once upfront, then reused for all file comparisons, avoiding redundant string operations.
9. Static Parsing Limitations
When falling back to static parsing, dependencies are extracted directly from pom.xml:
Limitations of static parsing:
[1.0,2.0))${project.version})10. Authentication Error Analysis
When Maven CLI fails, the detector analyzes error patterns:
If authentication errors are detected:
fallbackReason = MavenFallbackReason.AuthenticationFailureDetection Methods
MvnCliOnlyStaticParserOnlyMixedFallback Reasons
NoneMvnCliDisabledByUserCD_MAVEN_DISABLE_CLI=trueMavenCliNotAvailablemvncommand not found in PATHAuthenticationFailureOtherMvnCliFailureTelemetry
The detector records the following telemetry:
DetectionMethodMvnCliOnly,StaticParserOnly, orMixedFallbackReasonMvnCliComponentCountStaticParserComponentCountTotalComponentCountMavenCliAvailableOriginalPomFileCountFailedEndpointsTimedOuttrueif internal 5-minute timeout was reachedPrepareDetectionErrorVersion Property Resolution
Static parsing supports resolving version properties:
The resolver:
<properties>section in current documentUsage
As Part of Component Detection
The detector is registered as an experimental detector (
IExperimentalDetector) and runs automatically when Component Detection scans a directory containingpom.xmlfiles.Parallel Execution with MvnCliComponentDetector
Disabling Maven CLI (Static Parsing Only)
Set the environment variable to use only static parsing:
Experiment Configuration
The detector is paired with
MavenWithFallbackExperimentwhich compares results against the standardMvnCliComponentDetector:MvnCliComponentDetectorMavenWithFallbackDetectorEnable experiments via:
export CD_DETECTOR_EXPERIMENTS=truePerformance Characteristics
Comparison with Existing Detectors
Performance Optimizations
ActionBlockwith default settings (sequential) to avoid Maven repository lock contentionUnit Tests
The following unit tests are implemented in
MavenWithFallbackDetectorTests.cs:Maven CLI Scenarios
WhenMavenCliNotAvailable_FallsBackToStaticParsing_AsyncWhenMavenCliNotAvailable_DetectsMultipleDependencies_AsyncWhenMavenCliSucceeds_UsesMvnCliResults_AsyncWhenMavenCliSucceeds_PreservesTransitiveDependencies_AsyncWhenMavenCliProducesNoOutput_FallsBackToStaticParsing_AsyncStatic Parser Edge Cases
StaticParser_IgnoresDependenciesWithoutVersion_AsyncStaticParser_IgnoresDependenciesWithVersionRanges_Async[1.0,2.0)) are skippedStaticParser_ResolvesPropertyVersions_Async${property}versions are resolved from<properties>StaticParser_IgnoresDependenciesWithUnresolvablePropertyVersions_AsyncEmpty/Missing File Scenarios
WhenNoPomXmlFiles_ReturnsSuccessWithNoComponents_AsyncWhenPomXmlHasNoDependencies_ReturnsSuccessWithNoComponents_AsyncEnvironment Variable Control
WhenDisableMvnCliTrue_UsesStaticParsing_AsyncCD_MAVEN_DISABLE_CLI=truebypasses Maven CLI entirelyWhenDisableMvnCliEnvVarIsFalse_UsesMvnCliNormally_AsyncCD_MAVEN_DISABLE_CLI=falseallows Maven CLI to runWhenDisableMvnCliEnvVarNotSet_UsesMvnCliNormally_AsyncWhenDisableMvnCliEnvVarSetToInvalidValue_UsesMvnCliNormally_AsyncNested Pom.xml Handling
WhenMvnCliSucceeds_NestedPomXmlsAreFilteredOut_AsyncWhenMvnCliFailsCompletely_AllNestedPomXmlsAreRestoredForStaticParsing_AsyncWhenMvnCliPartiallyFails_NestedPomXmlsRestoredOnlyForFailedDirectories_AsyncError Analysis and Telemetry
WhenMvnCliFailsWithAuthError_LogsFailedEndpointAndSetsTelemetry_AsyncWhenMvnCliFailsWithNonAuthError_SetsFallbackReasonToOther_AsyncOtherMvnCliFailureFile Structure