@@ -48,16 +48,48 @@ public NugetPackageRestorer(
4848 missingPackageDirectory = new TemporaryDirectory ( ComputeTempDirectoryPath ( fileProvider . SourceDir . FullName , "missingpackages" ) , "missing package" , logger ) ;
4949 }
5050
51- public string ? TryRestoreLatestNetFrameworkReferenceAssemblies ( )
51+ public string ? TryRestore ( string package )
5252 {
53- if ( TryRestorePackageManually ( FrameworkPackageNames . LatestNetFrameworkReferenceAssemblies ) )
53+ if ( TryRestorePackageManually ( package ) )
5454 {
55- return DependencyManager . GetPackageDirectory ( FrameworkPackageNames . LatestNetFrameworkReferenceAssemblies , missingPackageDirectory . DirInfo ) ;
55+ var packageDir = DependencyManager . GetPackageDirectory ( package , missingPackageDirectory . DirInfo ) ;
56+ if ( packageDir is not null )
57+ {
58+ return GetNewestNugetPackageVersionFolder ( packageDir , package ) ;
59+ }
5660 }
5761
5862 return null ;
5963 }
6064
65+ public string GetNewestNugetPackageVersionFolder ( string packagePath , string packageFriendlyName )
66+ {
67+ var versionFolders = GetOrderedPackageVersionSubDirectories ( packagePath ) ;
68+ if ( versionFolders . Length > 1 )
69+ {
70+ var versions = string . Join ( ", " , versionFolders . Select ( d => d . Name ) ) ;
71+ logger . LogDebug ( $ "Found multiple { packageFriendlyName } DLLs in NuGet packages at { packagePath } . Using the latest version ({ versionFolders [ 0 ] . Name } ) from: { versions } .") ;
72+ }
73+
74+ var selectedFrameworkFolder = versionFolders . FirstOrDefault ( ) ? . FullName ;
75+ if ( selectedFrameworkFolder is null )
76+ {
77+ logger . LogDebug ( $ "Found { packageFriendlyName } DLLs in NuGet packages at { packagePath } , but no version folder was found.") ;
78+ selectedFrameworkFolder = packagePath ;
79+ }
80+
81+ logger . LogDebug ( $ "Found { packageFriendlyName } DLLs in NuGet packages at { selectedFrameworkFolder } .") ;
82+ return selectedFrameworkFolder ;
83+ }
84+
85+ public static DirectoryInfo [ ] GetOrderedPackageVersionSubDirectories ( string packagePath )
86+ {
87+ return new DirectoryInfo ( packagePath )
88+ . EnumerateDirectories ( "*" , new EnumerationOptions { MatchCasing = MatchCasing . CaseInsensitive , RecurseSubdirectories = false } )
89+ . OrderByDescending ( d => d . Name ) // TODO: Improve sorting to handle pre-release versions.
90+ . ToArray ( ) ;
91+ }
92+
6193 public HashSet < AssemblyLookupLocation > Restore ( )
6294 {
6395 var assemblyLookupLocations = new HashSet < AssemblyLookupLocation > ( ) ;
@@ -408,7 +440,8 @@ private static IEnumerable<string> GetRestoredPackageDirectoryNames(DirectoryInf
408440 . Select ( d => Path . GetFileName ( d ) . ToLowerInvariant ( ) ) ;
409441 }
410442
411- private bool TryRestorePackageManually ( string package , string ? nugetConfig = null , PackageReferenceSource packageReferenceSource = PackageReferenceSource . SdkCsProj , bool tryWithoutNugetConfig = true )
443+ private bool TryRestorePackageManually ( string package , string ? nugetConfig = null , PackageReferenceSource packageReferenceSource = PackageReferenceSource . SdkCsProj ,
444+ bool tryWithoutNugetConfig = true , bool tryPrereleaseVersion = true )
412445 {
413446 logger . LogInfo ( $ "Restoring package { package } ...") ;
414447 using var tempDir = new TemporaryDirectory (
@@ -430,59 +463,91 @@ private bool TryRestorePackageManually(string package, string? nugetConfig = nul
430463 return false ;
431464 }
432465
433- var res = dotnet . Restore ( new ( tempDir . DirInfo . FullName , missingPackageDirectory . DirInfo . FullName , ForceDotnetRefAssemblyFetching : false , PathToNugetConfig : nugetConfig ) ) ;
434- if ( ! res . Success )
466+ var res = TryRestorePackageManually ( package , nugetConfig , tempDir , tryPrereleaseVersion ) ;
467+ if ( res . Success )
435468 {
436- if ( tryWithoutNugetConfig && res . HasNugetPackageSourceError && nugetConfig is not null )
469+ return true ;
470+ }
471+
472+ if ( tryWithoutNugetConfig && res . HasNugetPackageSourceError && nugetConfig is not null )
473+ {
474+ logger . LogDebug ( $ "Trying to restore '{ package } ' without nuget.config.") ;
475+ // Restore could not be completed because the listed source is unavailable. Try without the nuget.config:
476+ res = TryRestorePackageManually ( package , nugetConfig : null , tempDir , tryPrereleaseVersion ) ;
477+ if ( res . Success )
437478 {
438- // Restore could not be completed because the listed source is unavailable. Try without the nuget.config:
439- res = dotnet . Restore ( new ( tempDir . DirInfo . FullName , missingPackageDirectory . DirInfo . FullName , ForceDotnetRefAssemblyFetching : false , PathToNugetConfig : null , ForceReevaluation : true ) ) ;
479+ return true ;
440480 }
481+ }
482+
483+ logger . LogInfo ( $ "Failed to restore nuget package { package } ") ;
484+ return false ;
485+ }
441486
442- // TODO: the restore might fail, we could retry with
443- // - a prerelease (*-* instead of *) version of the package,
444- // - a different target framework moniker.
487+ private RestoreResult TryRestorePackageManually ( string package , string ? nugetConfig , TemporaryDirectory tempDir , bool tryPrereleaseVersion )
488+ {
489+ var res = dotnet . Restore ( new ( tempDir . DirInfo . FullName , missingPackageDirectory . DirInfo . FullName , ForceDotnetRefAssemblyFetching : false , PathToNugetConfig : nugetConfig , ForceReevaluation : true ) ) ;
445490
446- if ( ! res . Success )
491+ if ( ! res . Success && tryPrereleaseVersion && res . HasNugetNoStablePackageVersionError )
492+ {
493+ logger . LogDebug ( $ "Failed to restore nuget package { package } because no stable version was found.") ;
494+ try
447495 {
448- logger . LogInfo ( $ "Failed to restore nuget package { package } ") ;
449- return false ;
496+ TryChangePackageVersion ( tempDir . DirInfo , "*-*" ) ;
497+
498+ res = dotnet . Restore ( new ( tempDir . DirInfo . FullName , missingPackageDirectory . DirInfo . FullName , ForceDotnetRefAssemblyFetching : false , PathToNugetConfig : nugetConfig , ForceReevaluation : true ) ) ;
499+ return res ;
500+ }
501+ finally
502+ {
503+ TryChangePackageVersion ( tempDir . DirInfo , "*" ) ;
450504 }
451505 }
452506
453- return true ;
507+ return res ;
454508 }
455509
456510 private void TryChangeTargetFrameworkMoniker ( DirectoryInfo tempDir )
511+ {
512+ TryChangeProjectFile ( tempDir , TargetFramework ( ) , $ "<TargetFramework>{ FrameworkPackageNames . LatestNetFrameworkMoniker } </TargetFramework>", "target framework moniker" ) ;
513+ }
514+
515+ private void TryChangePackageVersion ( DirectoryInfo tempDir , string newVersion )
516+ {
517+ TryChangeProjectFile ( tempDir , PackageReferenceVersion ( ) , $ "Version=\" { newVersion } \" ", "package reference version" ) ;
518+ }
519+
520+ private bool TryChangeProjectFile ( DirectoryInfo projectDir , Regex pattern , string replacement , string patternName )
457521 {
458522 try
459523 {
460- logger . LogInfo ( $ "Changing the target framework moniker in { tempDir . FullName } ...") ;
524+ logger . LogDebug ( $ "Changing the { patternName } in { projectDir . FullName } ...") ;
461525
462- var csprojs = tempDir . GetFiles ( "*.csproj" , new EnumerationOptions { RecurseSubdirectories = false , MatchCasing = MatchCasing . CaseInsensitive } ) ;
526+ var csprojs = projectDir . GetFiles ( "*.csproj" , new EnumerationOptions { RecurseSubdirectories = false , MatchCasing = MatchCasing . CaseInsensitive } ) ;
463527 if ( csprojs . Length != 1 )
464528 {
465- logger . LogError ( $ "Could not find the .csproj file in { tempDir . FullName } , count = { csprojs . Length } ") ;
466- return ;
529+ logger . LogError ( $ "Could not find the .csproj file in { projectDir . FullName } , count = { csprojs . Length } ") ;
530+ return false ;
467531 }
468532
469533 var csproj = csprojs [ 0 ] ;
470534 var content = File . ReadAllText ( csproj . FullName ) ;
471- var matches = TargetFramework ( ) . Matches ( content ) ;
535+ var matches = pattern . Matches ( content ) ;
472536 if ( matches . Count == 0 )
473537 {
474- logger . LogError ( $ "Could not find target framework in { csproj . FullName } ") ;
475- }
476- else
477- {
478- content = TargetFramework ( ) . Replace ( content , $ "<TargetFramework>{ FrameworkPackageNames . LatestNetFrameworkMoniker } </TargetFramework>", 1 ) ;
479- File . WriteAllText ( csproj . FullName , content ) ;
538+ logger . LogError ( $ "Could not find the { patternName } in { csproj . FullName } ") ;
539+ return false ;
480540 }
541+
542+ content = pattern . Replace ( content , replacement , 1 ) ;
543+ File . WriteAllText ( csproj . FullName , content ) ;
544+ return true ;
481545 }
482546 catch ( Exception exc )
483547 {
484- logger . LogError ( $ "Failed to update target framework in { tempDir . FullName } : { exc } ") ;
548+ logger . LogError ( $ "Failed to change the { patternName } in { projectDir . FullName } : { exc } ") ;
485549 }
550+ return false ;
486551 }
487552
488553 private static async Task ExecuteGetRequest ( string address , HttpClient httpClient , CancellationToken cancellationToken )
@@ -664,6 +729,9 @@ private IEnumerable<string> GetFeeds(Func<IList<string>> getNugetFeeds)
664729 [ GeneratedRegex ( @"<TargetFramework>.*</TargetFramework>" , RegexOptions . IgnoreCase | RegexOptions . Compiled | RegexOptions . Singleline ) ]
665730 private static partial Regex TargetFramework ( ) ;
666731
732+ [ GeneratedRegex ( @"Version=""[*|*-*]""" , RegexOptions . IgnoreCase | RegexOptions . Compiled | RegexOptions . Singleline ) ]
733+ private static partial Regex PackageReferenceVersion ( ) ;
734+
667735 [ GeneratedRegex ( @"^(.+)\.(\d+\.\d+\.\d+(-(.+))?)$" , RegexOptions . IgnoreCase | RegexOptions . Compiled | RegexOptions . Singleline ) ]
668736 private static partial Regex LegacyNugetPackage ( ) ;
669737
0 commit comments