NuGet / Home

Repo for NuGet Client issues
Other
1.49k stars 250 forks source link

NuGet update fails with Method not found on Microsoft.IO.Path.GetFileName #12848

Closed Malcolmnixon closed 1 year ago

Malcolmnixon commented 1 year ago

NuGet Product Used

NuGet.exe

Product Version

6.6.1.2

Worked before?

No response

Impact

I'm unable to use this version

Repro Steps & Context

  1. Extract zip file (C++ vcxproj/solution)
  2. Run nuget restore to restore packages
  3. Run nuget update test.sln and (Generates Method not found exception)
  4. Run nuget update test.sln -MSBuildVersion 14 and observe command works

test.zip

Verbose Logs

NuGet Version: 6.6.1.2
MSBuild auto-detection: using msbuild version '17.7.2.37605' from 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\bin'. Use option -MSBuildVersion to force nuget to use a specific version of MSBuild.
Scanning for projects...
Method not found: 'System.ReadOnlySpan`1<Char> Microsoft.IO.Path.GetFileName(System.ReadOnlySpan`1<Char>)'.
System.MissingMethodException: Method not found: 'System.ReadOnlySpan`1<Char> Microsoft.IO.Path.GetFileName(System.ReadOnlySpan`1<Char>)'.
   at Microsoft.Build.Shared.FileMatcher.IsFileNameMatch(String path, String pattern)
   at System.Linq.Enumerable.WhereListIterator`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Microsoft.Build.Shared.FileMatcher.GetFilesForStep(RecursiveStepResult stepResult, RecursionState recursionState, String projectDirectory, Boolean stripProjectDirectory)
   at Microsoft.Build.Shared.FileMatcher.GetFilesRecursive(ConcurrentStack`1 listOfFiles, RecursionState recursionState, String projectDirectory, Boolean stripProjectDirectory, IList`1 searchesToExclude, Dictionary`2 searchesToExcludeInSubdirs, TaskOptions taskOptions)
   at Microsoft.Build.Shared.FileMatcher.GetFilesImplementation(String projectDirectoryUnescaped, String filespecUnescaped, List`1 excludeSpecsUnescaped)
   at Microsoft.Build.Shared.FileMatcher.<>c__DisplayClass67_0.<GetFiles>b__1(String _)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Build.Shared.FileMatcher.GetFiles(String projectDirectoryUnescaped, String filespecUnescaped, List`1 excludeSpecsUnescaped)
   at Microsoft.Build.Internal.EngineFileUtilities.GetFileList(String directoryEscaped, String filespecEscaped, Boolean returnEscaped, Boolean forceEvaluateWildCards, IEnumerable`1 excludeSpecsEscaped, FileMatcher fileMatcher, Object loggingMechanism, IElementLocation includeLocation, IElementLocation excludeLocation, IElementLocation importLocation, BuildEventContext buildEventContext, String buildEventFileInfoFullPath, Boolean disableExcludeDriveEnumerationWarning)
   at Microsoft.Build.Internal.EngineFileUtilities.GetFileListEscaped(String directoryEscaped, String filespecEscaped, IEnumerable`1 excludeSpecsEscaped, Boolean forceEvaluate, FileMatcher fileMatcher, Object loggingMechanism, IElementLocation includeLocation, IElementLocation excludeLocation, IElementLocation importLocation, BuildEventContext buildEventContext, String buildEventFileInfoFullPath, Boolean disableExcludeDriveEnumerationWarning)
   at Microsoft.Build.Evaluation.Evaluator`4.ExpandAndLoadImportsFromUnescapedImportExpression(String directoryOfImportingFile, ProjectImportElement importElement, String unescapedExpression, Boolean throwOnFileNotExistsError, List`1& imports)
   at Microsoft.Build.Evaluation.Evaluator`4.ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(String directoryOfImportingFile, ProjectImportElement importElement, List`1& projects, SdkResult& sdkResult)
   at Microsoft.Build.Evaluation.Evaluator`4.ExpandAndLoadImports(String directoryOfImportingFile, ProjectImportElement importElement, SdkResult& sdkResult)
   at Microsoft.Build.Evaluation.Evaluator`4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement)
   at Microsoft.Build.Evaluation.Evaluator`4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)
   at Microsoft.Build.Evaluation.Evaluator`4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement)
   at Microsoft.Build.Evaluation.Evaluator`4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport)
   at Microsoft.Build.Evaluation.Evaluator`4.Evaluate()
   at Microsoft.Build.Evaluation.Evaluator`4.Evaluate(IEvaluatorData`4 data, Project project, ProjectRootElement root, ProjectLoadSettings loadSettings, Int32 maxNodeCount, PropertyDictionary`1 environmentProperties, ILoggingService loggingService, IItemFactory`2 itemFactory, IToolsetProvider toolsetProvider, IDirectoryCacheFactory directoryCacheFactory, ProjectRootElementCacheBase projectRootElementCache, BuildEventContext buildEventContext, ISdkResolverService sdkResolverService, Int32 submissionId, EvaluationContext evaluationContext, Boolean interactive)
   at Microsoft.Build.Evaluation.Project.ProjectImpl.Reevaluate(ILoggingService loggingServiceForEvaluation, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext)
   at Microsoft.Build.Evaluation.Project.ProjectImpl.ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext)
   at Microsoft.Build.Evaluation.Project.ProjectImpl.ReevaluateIfNecessary(EvaluationContext evaluationContext)
   at Microsoft.Build.Evaluation.Project.ProjectImpl.Initialize(IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext, Boolean interactive)
   at Microsoft.Build.Evaluation.Project..ctor(String projectFile, IDictionary`2 globalProperties, String toolsVersion, String subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, Boolean interactive)
   at Microsoft.Build.Evaluation.Project..ctor(String projectFile)
Malcolmnixon commented 1 year ago

Modern versions of MSBuild such as 15.1.0.0 which ship with Visual Studio 17.7.2 are still using .NET Framework 4.7.2; however they have added additional libraries such as Microsoft.IO.Redist to provide modern methods to older code bases.

In this instance the Microsoft.Build.dll (15.1.0.0) has a reference to Microsoft.IO.Redist.dll (6.0.0.0) and makes use of ReadOnlySpan<char> Microsoft.IO.Path.GetFileName(ReadOnlySpan<char> path).

When nuget update <solution> executes, MSBuildUser::LoadAssemblies executes Assembly.Load() on the Microsoft.Build.dll (15.1.0.0), which works, but does not load the referenced Microsoft.IO.Redist.dll (6.0.0.0) assembly. Then in MSBuildProjectSystem::GetProject() it tries to create an instance of the Microsoft.Build.Evaluation.Project from the Microsoft.Build.dll (15.1.0.0), however due to the failure to load the referenced assemblies it gets the following exception:

System.Reflection.TargetInvocationException
  HResult=0x80131604
  Message=Exception has been thrown by the target of an invocation.
  Source=mscorlib
  StackTrace:
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at NuGet.Common.MSBuildProjectSystem.GetProject(String projectFile) in NuGet.Client\src\NuGet.Clients\NuGet.CommandLine\Common\MSBuildProjectSystem.cs:line 448

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
MissingMethodException: Method not found: 'System.ReadOnlySpan`1<Char> Microsoft.IO.Path.GetFileName(System.ReadOnlySpan`1<Char>)'.

The problem appears to be not loading referenced assemblies, however https://github.com/NuGet/NuGet.Client/pull/4975 was supposed to fix this problem. It may be the nuget update path does not correctly hook the AppDomain.CurrentDomain.AssemblyResolve events and handle them appropriately.

zivkan commented 1 year ago

@Malcolmnixon firstly, thank you for creating a sample project/solution and attaching it. Surprisingly few customers reporting problems provide us with a repro to run locally.

Unfortunately, I'm not able to reproduce the error. I was sure to try to test the most similar setup as you, VS 17.7 and nuget.exe 6.6.1. However, it works for me:

MSBuild auto-detection: using msbuild version '17.7.2.37605' from 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\amd64'.
Scanning for projects...
Found 1 project with a packages.config file. (test)
Feeds used:
  https://api.nuget.org/v3/index.json
  C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\

Attempting to gather dependency information for multiple packages with respect to project 'D:\src\test\nugetUpdate\test\test.vcxproj', targeting 'native,Version=v0.0'
Gathering dependency information took 323 ms
Attempting to resolve dependencies for multiple packages.
Resolving dependency information took 0 ms
Resolving actions install multiple packages
Resolution was successful but resulted in no action
There are no new updates available.
No package updates are available from the current package source for project 'D:\src\test\nugetUpdate\test\test.vcxproj'.
Executing nuget actions took 6 ms

The error message you copied into this issue, was it from running the test.sln you provided, or was it actually from your "real" project, and you assumed it'd still happen with test.sln? If you run nuget.exe from within a "Developer Command Prompt for VS" or "Developer PowerShell for VS", does it still happen?

If you happen to have experience using fuslogvw, I think it would be interesting to see which which dlls were being loaded, and their full paths. As you pointed out, this happens when Microsoft.Build.dll or Microsoft.IO.Redist.dll were compiled against a different version of Microsoft.Memory.dll than was loaded. There's a community created fuslog++ which I've heard is much easier to use, but I've never tried it myself. Another option is to use PerfView.exe to record nuget.exe's execution. I don't remember if it's necessary to enable the ".NET Loader" collector to include assembly load information, but it wouldn't hurt. However, the etl.zip file it will record will be huge, so fuslog will result in much smaller logs to share.

Looking at the vcxproj, I think this is not a C++/CLI project, so it's unlikely you have microsort.build.dll, system.memory.dll or microsoft.io.redist.dll is your project's bin\ directory. I'm not sure what else the problem might be,

Malcolmnixon commented 1 year ago

Hi @zivkan, I just replicated the issue by:

  1. Extracting the zip file above to C:\src\test
  2. Opening a command prompt in the folder and running nuget restore
    
    C:\src\test>nuget restore
    MSBuild auto-detection: using msbuild version '17.7.2.37605' from 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\bin'.
    Restoring NuGet package gtest.1.7.0.
    Adding package 'gtest.1.7.0' to folder 'C:\src\test\packages'
    Added package 'gtest.1.7.0' to folder 'C:\src\test\packages'

NuGet Config files used: C:\Users\mnixon\AppData\Roaming\NuGet\NuGet.Config C:\Program Files (x86)\NuGet\Config\Microsoft.VisualStudio.Offline.config C:\Program Files (x86)\NuGet\Config\Xamarin.Offline.config

Feeds used: C:\Users\mnixon.nuget\packages\ https://api.nuget.org/v3/index.json C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\

Installed: 1 package(s) to packages.config projects


3. Then running `nuget update test.sln`

C:\src\test>nuget update test.sln MSBuild auto-detection: using msbuild version '17.7.2.37605' from 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\bin'. Scanning for projects... Method not found: 'System.ReadOnlySpan1<Char> Microsoft.IO.Path.GetFileName(System.ReadOnlySpan1)'.



---

Update: The problem appears to be that the NuGet 6.6.1.2 I was using happened to be in a folder that also contained the `System.Memory.dll (4.5.5)` for net461. If I copy the NuGet.exe to a different folder and put that folder earlier in the path then everything seems to work.
zivkan commented 1 year ago

Thanks for getting back to us!

There is some docs on how the .NET Framework runtime locates assemblies: https://learn.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies

To be honest, I didn't read it carefully. Doing very quick search, it appears to say that if the referenced assembly is not strong named, then the runtime only checks the application directory, and later when it gives a 4 step list on how it loads strong named assemblies, it doesn't mention the application directory.

In any case, I'm going to close is as by design, as this is just how the .NET Framework runtime works.