Статьи

Установка NuGet не работает с F #

При попытке использовать NuGet для добавления ссылки на пакет в проект F # возникает очень неприятная ошибка. Это проявляется, когда либо устанавливаемая сборка также имеет версию в GAC, либо другая версия уже существует в выходном каталоге.

Сначала давайте воспроизведем проблему, когда версия сборки уже существует в GAC.

Создайте новое решение с проектом F #.

Выберите сборку, которую вы хотите установить, из NuGet, которая также существует в GAC на вашем компьютере. Для иронических целей я выберу NuGet.Core для этого примера.

Это в моем GAC:

D:\>gacutil -l | find "NuGet.Core"
  NuGet.Core, Version=1.0.11220.104, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL
  NuGet.Core, Version=1.6.30117.9648, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL

Вы можете видеть, что самая высокая версия в GAC — это версия 1.6.30117.9648.

Теперь давайте установим NuGet.Core версии 2.5.0 из официального источника NuGet:

PM> Install-Package NuGet.Core -Version 2.5.0
Installing 'Nuget.Core 2.5.0'.
Successfully installed 'Nuget.Core 2.5.0'.
Adding 'Nuget.Core 2.5.0' to Mike.NuGetExperiments.FsProject.
Successfully added 'Nuget.Core 2.5.0' to Mike.NuGetExperiments.FsProject.

Он правильно создает каталог пакетов, загружает пакет NuGet.Core и создает файл packages.config:

D:\Source\Mike.NuGetExperiments\src>tree /F
D:.
│   Mike.NuGetExperiments.sln
│
├───Mike.NuGetExperiments.FsProject
│   │   Mike.NuGetExperiments.FsProject.fsproj
│   │   packages.config
│   │   Spike.fs
│   │
│   ├───bin
│   │   └───Debug
│   │
│   └───obj
│       └───Debug
│
└───packages
    │   repositories.config
    │
    └───Nuget.Core.2.5.0
        │   Nuget.Core.2.5.0.nupkg
        │   Nuget.Core.2.5.0.nuspec
        │
        └───lib
            └───net40-Client
                    NuGet.Core.dll

Но когда я смотрю на свой файл fsproj, я вижу, что он неправильно ссылается на версию NuGet.Core (1.6.30117.9648) из GAC, и нет пути к подсказке, указывающей на загруженный пакет.

<Reference Include="NuGet.Core, Version=1.6.30117.9648, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
  <Private>True</Private>
</Reference>

Теперь давайте воспроизведем проблему, когда версия сборки уже существует в выходном каталоге.

На этот раз я собираюсь использовать EasyNetQ в качестве примера DLL. Сначала я собираюсь взять последнюю версию EasyNetQ.dll, 0.10.1.92 и перенести ее в выходной каталог проектов (bin \ Debug).

Далее используйте NuGet для установки более ранней версии сборки:

Install-Package EasyNetQ -Version 0.9.2.76
Attempting to resolve dependency 'RabbitMQ.Client (= 3.0.2.0)'.
Attempting to resolve dependency 'Newtonsoft.Json (≥ 4.5)'.
Installing 'RabbitMQ.Client 3.0.2'.
Successfully installed 'RabbitMQ.Client 3.0.2'.
Installing 'Newtonsoft.Json 4.5.11'.
Successfully installed 'Newtonsoft.Json 4.5.11'.
Installing 'EasyNetQ 0.9.2.76'.
Successfully installed 'EasyNetQ 0.9.2.76'.
Adding 'RabbitMQ.Client 3.0.2' to Mike.NuGetExperiments.FsProject.
Successfully added 'RabbitMQ.Client 3.0.2' to Mike.NuGetExperiments.FsProject.
Adding 'Newtonsoft.Json 4.5.11' to Mike.NuGetExperiments.FsProject.
Successfully added 'Newtonsoft.Json 4.5.11' to Mike.NuGetExperiments.FsProject.
Adding 'EasyNetQ 0.9.2.76' to Mike.NuGetExperiments.FsProject.
Successfully added 'EasyNetQ 0.9.2.76' to Mike.NuGetExperiments.FsProject.

NuGet сообщает, что все прошло по плану и что EasyNetQ  0.9.2.76  был успешно добавлен в мой проект.

Еще раз каталог пакетов был успешно создан и была загружена правильная версия EasyNetQ. Файл packages.config также имеет правильную версию EasyNetQ. Я больше не буду показывать вам вывод из дерева, он почти такой же, как и раньше.

Опять же, когда я смотрю на свой файл fsproj, версия EasyNetQ неверна, это 0.10.1.92, и снова нет пути подсказки:

<Reference Include="EasyNetQ, Version=0.10.1.92, Culture=neutral, PublicKeyToken=null">
  <Private>True</Private>
</Reference>

Да, установка NuGet определенно не работает с F #.

Эта ошибка делает использование NuGet и F # вместе разочарованием. Наша команда потратила впустую дни, пытаясь разобраться в этом.

Кажется, это хорошо известная проблема. Просто взгляните на этот рабочий объект, о котором сообщалось более года назад:

http://nuget.codeplex.com/workitem/2149

После долгих проклятий в NuGet проблема, по-видимому, связана с системой проектов F #, а не с самим NuGet:

«F # знает об этом поведении, и они выпустят исправление»

Хм, это еще не исправлено.

Мы покопались в коде NuGet. Интересный фрагмент этого фрагмента файла (из NuGet.VisualStudio.VsProjectSystem):

    public virtual void AddReference(string referencePath, Stream stream)
    {
        string name = Path.GetFileNameWithoutExtension(referencePath);
        try
        {
            // Get the full path to the reference
            string fullPath = PathUtility.GetAbsolutePath(Root, referencePath);
            string assemblyPath = fullPath;
     
           ...
    
           // Add a reference to the project
           dynamic reference = Project.Object.References.Add(assemblyPath);
    
           ...
    
           TrySetCopyLocal(reference);
    
           // This happens if the assembly appears in any of the search
           // paths that VS uses to locate assembly references. Most commonly, 
           // it happens if this assembly is in the GAC or in the output path.
           if (!reference.Path.Equals(fullPath, StringComparison.OrdinalIgnoreCase))
           {
               // Get the msbuild project for this project
               MsBuildProject buildProject = Project.AsMSBuildProject();
    
               if (buildProject != null)
               {
                   // Get the assembly name of the reference we are trying to add
                   AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath);
    
                   // Try to find the item for the assembly name
                   MsBuildProjectItem item = 
                       (from assemblyReferenceNode in buildProject.GetAssemblyReferences()
                       where AssemblyNamesMatch(assemblyName, assemblyReferenceNode.Item2)
                       select assemblyReferenceNode.Item1).FirstOrDefault();
    
                   if (item != null)
                   {
                       // Add the <HintPath> metadata item as a relative path
                       item.SetMetadataValue("HintPath", referencePath);
    
                       // Save the project after we've modified it.
                       Project.Save();
                   }
               }
           }
       }
       catch (Exception e)
       {
           ...
       }
   }

On line 13 NuGet calls out to the F# project system and asks it to add a reference to the assembly at the given path. We assume that the F# project system then does the wrong thing by searching for the assembly name anywhere in the GAC or the output directory rather than referencing the explicit assembly NuGet is asking it to reference.

Interestingly, it looks as if the NuGet team have attempted to code a work-around for this bug from line 22 onwards. Could this be why C# projects don’t exhibit this behaviour? Unfortunately the work around doesn’t work in the F# case. We think it’s because F# doesn’t respect assembly versions and will happily replace any requested assembly with another one so long as it’s got the same simple name. At line 33, no assemblies are found in the fsproj file because the ‘AssemblyNamesMatch’ function does an exact match using all four elements of the full assembly name (simple name, version, culture, and key) and of course the assembly that the F# project system has found and added has a different version.

So, come on F# team, pull your finger out and fix the Visual Studio F# project system. In the meantime, in my next post I’ll talk about some of things our team, and especially the excellent Michael Newton (@mavnn) has been doing to try and work around these problems.