dotnet/fsharp

PackageReference not working correctly in fsproj old format - Visual Studio (Windows)

TimLariviere opened this issue · 11 comments

Visual Studio (Windows) doesn't display correctly the packages referenced with PackageReference in an F# project old format.
Also Package Manager UI (Manage NuGet packages...) will ignore the existing packages and try to create a new packages.config file (even with the option to prefer PackageReference).

Note that the same F# project will work as expected in Visual Studio for Mac.
Both msbuild Windows and MacOS will successfully compile the project.

Visual Studio will only compile after doing one compilation with msbuild.

An equivalent C# project will work fine.

Repro steps

Here's a repro: https://github.com/TimLariviere/XamarinFormsPackageReference
Opening the repro solution with Visual Studio (Windows) is enough to trigger the issue.

Otherwise here are the steps I used:

  1. dotnet new -i Fabulous.Templates (Fabulous is an F# framework for Xamarin.Forms)
  2. dotnet new fabulous-app -n ProjectName
  3. Open the created solution with Visual Studio
  4. Convert the Android and iOS projects manually from packages.config to PackageReference (no migration tool available)
    a. In the packages.config file, remove all lines that do not start with <package id
    b. Change all lines from <package id="XXX" version="X.Y.Z.W" /> to <PackageReference Include="XXX" Version="X.Y.Z.W" />
    c. Copy all the lines to the fsproj ProjectName.Android and replace all previous references that pointed to the packages folder
    d. Remove all props and targets files that pointed to the packages folder
    e. Remove <None Include="packages.config" /> and delete the related file
  5. Reload the project in Visual Studio
  6. The display issue will appear
  7. Try to build the Android project, should be build failed
  8. Run the following commands nuget restore and msbuild, should be build success
  9. Try to build the Android project, should be build success

Expected behavior

It should work the same as a C# project (old format), meaning:

  • Correctly displaying packages under References node
  • Packages taken into account when restoring with Visual Studio or managing dependencies through the Package Manager UI.

(This solution has been created with the built-in Xamarin.Forms C# template in Visual Studio)

Actual behavior

Currently the packages appear as unknown files at the root of the project.

The Package Manager UI ignores the packages and if we try to add a new package, it will try to create a new packages.config file.

fsharp-oldformat-manager

Known workarounds

No workaround, except for manually running nuget restore and msbuild to ensure a successful build before debugging with Visual Studio.

Other solutions like MSBuild Extras SDK which allows to convert an old format fsproj to a new format would be a good workaround only if Visual Studio (Windows) and Visual Studio for Mac allow to run a converted Xamarin project.

Related information

  • Tested with Windows 10 (Version 10.0.17134.345) and macOS Mojave (Version 10.14)
  • Visual Studio 15.8.6 and Visual Studio for Mac 7.6.10
  • Severity: High - Not blocking but not usable for open source libraries and templates

Visual Studio being the primarily used IDE for .NET, this issue prevents to use PackageReference in old format projects.
It's not blocking thanks to the workaround, but it can't be used for open source libraries or templates such as Fabulous (https://github.com/fsprojects/Fabulous). This would impact badly the adoption of the framework, as users would expect it to work with Visual Studio immediately.

In the meantime, we're stuck with packages.config which has a few issues with dependency transitivity.

I don't expect we'll backport PackageReference support for the legacy project system, as our goal is to move off of it entirely. Doing that work in CSProj.dll was nontrivial and had a bit of a bug tail as well. We'll likely block creation of new projects with this older format in VS 2019, and we'll aim to have all older projects open in the newer project system so users can more easily trim their projects to the newer format. I suggest that the templates for F# project use packages.config and old-style NuGet references if they cannot adopt the new project file format.

That said, I think we'd certainly accept a PR that would add this support.

Who can explain at least where to look for C# implementation?

The C# implementation is closed source.

and we'll aim to have all older projects open in the newer project system so users can more easily trim their projects to the newer format

@cartermp, How can this be done? I mean, if you have the legacy format (in my case: 50 or so projects), I'd love to change to the "new format", but I've no idea how. Visual Studio doesn't magically seem do it (yet). And I've no idea how to manually, or semi-automatically do it.

Meanwhile, I'm struggling with keeping all references in-sync with NuGet + packages.config, which is not supported anymore. This kinda drags me down and I'm probably not the only one. I can understand if this is not going to be an automated task (either move to package-reference, or the move to the new project format), but I'm at a loss how I can do it.

Apart from setting up the whole project from scratch, and copy all the files back into it one by one or so.

@abelbraaksma Your best bet is probably this: https://github.com/dotnet/try-convert

But there's some weird subtlety with desktop MSBuild and F# projects that can cause it to fail for legacy F# project files in my experience. There will not be any tooling to support this, since there is no 1:1 mapping for conversion and there will always be behavior changes. Let alone numerous cases where things simply work differently, or fail and force you to do things differently.

@cartermp is there a migration guide for F# Xamarin projects?

@xperiandri No, there is no guide. Currently there are no Xamarin projects that support .NET SDK-style formats, either.

@cartermp If there is a crying reaction I will give you one rn 😭

@cartermp thanks for the input. I guess I'll just have to bite the bullet sometime soon. I also saw a post by Scott Hanselmann to just start from scratch and add the structure back into clean new style projects. Though a valuable hint was to use wild cards for inclusion of files, like **/*, which may save some time.

Btw, my main reason for considering this is a weird NuGet behavior when your project includes references to both System.Memory and System.Text.Json. Both reference System.Numerics.Vectors, but resp. 4.4 (4.1.4.0) and 4.5 (4.1.5.0).

Normally that isn't a problem, because Private should be set to True, and HintPath to the package location. This works in csproj files correctly (done by NuGet), but not in fsproj, which doesn't set either. Instead, it creates a generic reference, which msbuild will resolve to the version installed in GAC by the .NET Framework (being 4.1.4.0). This leads to warnings on build and runtime errors in some cases, depending what methods you call.

Typically this can be resolved with automatic binding redirects, but since nothing gets copied to the output on build, this has no effect.

Solution is to fix the fsproj manually, and add the binding redirects manually. But each time NuGet restores or updates, this is reverted.

I'm not sure these dependencies are resolved correctly in the new project format. My first attempt was to use the old format with package references, but that only works for csproj. Since neither NuGet, nor F# is going to fix these old bugs, I'm stuck with doing it manually (ok, just created a transform script in XSLT 3.0, that eases the pain a little).

This will happen in the future again, I'm afraid, and not just with my projects. Hence the only way forward is at some point to migrate.

Since all of Xamarin is moving to SDK-style projects I don't think we'll take an enhacement here, since packagereference works without issue.