53 ArgumentNullException.ThrowIfNull(
75 CancellationToken cancellationToken =
default)
77 ArgumentNullException.ThrowIfNull(
90 cancellationToken.ThrowIfCancellationRequested();
92 IReadOnlyList<SolverDiscoveryCandidate> candidates =
93 _candidateCollector.Collect(
100 cancellationToken.ThrowIfCancellationRequested();
102 result.Candidates.Add(
106 DiscoverAdapterDescriptors(
111 BuildAvailabilityInformation(
118 result.CompletedAtUtc =
121 result.ElapsedSeconds =
122 stopwatch.Elapsed.TotalSeconds;
124 result.AddDiagnostic(
125 $
"Solver discovery completed with " +
126 $
"{result.Candidates.Count} installation " +
128 $
"{result.AdapterDescriptors.Count} adapter " +
129 $
"descriptors, and " +
130 $
"{result.AvailabilityInformation.Count} " +
131 "solver availability records.");
133 return ValueTask.FromResult(
136 catch (OperationCanceledException)
140 result.CompletedAtUtc =
143 result.ElapsedSeconds =
144 stopwatch.Elapsed.TotalSeconds;
146 result.AddDiagnostic(
147 "Solver discovery was cancelled.");
149 return ValueTask.FromResult(
152 catch (Exception exception)
156 result.CompletedAtUtc =
159 result.ElapsedSeconds =
160 stopwatch.Elapsed.TotalSeconds;
162 result.AddDiagnostic(
163 $
"Solver discovery failed: {exception.Message}");
165 result.AddDiagnostic(
166 exception.ToString());
168 return ValueTask.FromResult(
173 private static void DiscoverAdapterDescriptors(
176 CancellationToken cancellationToken)
180 in GetAdapterSearchDirectories(
183 cancellationToken.ThrowIfCancellationRequested();
185 if (!Directory.Exists(
191 SearchOption searchOption =
192 options.RecursiveAdapterSearch
193 ? SearchOption.AllDirectories
194 : SearchOption.TopDirectoryOnly;
196 IEnumerable<string> assemblyPaths;
201 Directory.EnumerateFiles(
203 "LotSizingDataModel.Solver.*.dll",
210 UnauthorizedAccessException or
214 $
"Adapter directory '{directory}' could not " +
215 $
"be scanned: {exception.Message}");
224 cancellationToken.ThrowIfCancellationRequested();
226 if (!TryInferSolverKind(
240 descriptor.AssemblyPath,
242 StringComparison.OrdinalIgnoreCase)))
259 SolverKind.CoinOrCbc =>
263 solverKind.ToString()
267 new SolverAdapterDescriptor
270 $
"LotSizingDataModel.Solver." +
274 $
"{shortName} solver adapter",
277 GetAssemblyVersionHint(
296 private static void BuildAvailabilityInformation(
297 IReadOnlyList<SolverDiscoveryCandidate> candidates,
298 SolverDiscoveryOptions options,
299 SolverDiscoveryResult result)
303 in GetConcreteSolverKinds())
305 SolverDiscoveryCandidate? bestCandidate =
309 candidate.SolverKind ==
318 new SolverAvailabilityInfo(
320 bestCandidate is
null
322 : options.ValidateLicenses
324 .AvailableWithLimitations
332 bestCandidate?.Path ??
336 bestCandidate?.VersionHint ??
340 if (bestCandidate is
null)
342 availability.AddDiagnostic(
343 "No existing installation candidate was " +
346 result.AvailabilityInformation.Add(
352 availability.AddDiagnostic(
353 $
"Installation candidate detected from " +
354 $
"{bestCandidate.Source}: " +
355 $
"'{bestCandidate.Path}'.");
357 if (options.ValidateLicenses)
359 availability.AddLimitation(
360 "Native solver and license validation is " +
361 "deferred to the vendor-specific adapter.");
363 availability.AddDiagnostic(
364 "The generic solver project intentionally " +
365 "does not load vendor SDKs during discovery.");
368 result.AvailabilityInformation.Add(
373 private static IReadOnlyList<string>
374 GetAdapterSearchDirectories(
375 SolverDiscoveryOptions options)
380 string applicationDirectory =
381 AppContext.BaseDirectory;
383 if (options.SearchApplicationDirectory)
386 applicationDirectory);
389 if (options.SearchPluginSubdirectory)
393 applicationDirectory,
394 options.PluginSubdirectoryName));
397 directories.AddRange(
398 options.AdapterSearchDirectories);
403 !
string.IsNullOrWhiteSpace(
408 Environment.ExpandEnvironmentVariables(
411 StringComparer.OrdinalIgnoreCase)
415 private static bool TryInferSolverKind(
420 Path.GetFileNameWithoutExtension(
423 if (fileName.EndsWith(
425 StringComparison.OrdinalIgnoreCase))
433 if (fileName.EndsWith(
435 StringComparison.OrdinalIgnoreCase))
443 if (fileName.EndsWith(
445 StringComparison.OrdinalIgnoreCase))
453 if (fileName.EndsWith(
455 StringComparison.OrdinalIgnoreCase) ||
458 StringComparison.OrdinalIgnoreCase) ||
461 StringComparison.OrdinalIgnoreCase))
475 private static string GetAssemblyVersionHint(
480 FileVersionInfo versionInfo =
481 FileVersionInfo.GetVersionInfo(
484 if (!
string.IsNullOrWhiteSpace(
485 versionInfo.FileVersion))
487 return versionInfo.FileVersion;
495 UnauthorizedAccessException or
505 private static string GetSolverName(
508 return solverKind
switch
517 "FICO Xpress Optimizer",
519 SolverKind.CoinOrCbc =>
523 solverKind.ToString()
527 private static IReadOnlyList<SolverKind>
528 GetConcreteSolverKinds()