How to use F# on CI server when using the new standalone installer for F# 3.1.1

It looks to me like the choose clause might actually be incorrect. I would think that the $(VisualStudioVersion) would be empty '' if there was no visual studio install. However the $(FSharpTargetsPath) that points to is where I would expect the standalone targets file to reside. Obviously swapping out the 3.0 for a 3.1. see below.

  <Choose>
    <When Condition="'$(VisualStudioVersion)' == '11.0'">
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>
      <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets')">
        <FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
      </PropertyGroup>          
    </Otherwise>
  </Choose>

Disclaimer: this is pure guess work as I'm not at a PC where I can try this.


This is a bug in the installer for 3.1.1.

Summary

This affects only clean-machine installs. Run the script at the bottom, and things will start working, no changes needed to your project.

Details

As of VS 2013, F# project templates contain the Choose snippet as you've provided in your question. The design is as follows:

  • The initial check for VisualStudioVersion = 11.0 is for back-compat support with VS 2012: believe it or not, a new VS 2013 F# project will open fine in both VS 2012 and 2013 provided you are targeting F# 3.0. The F# 3.0 build targets file is used when the project is open in VS 2012 (aka version 11.0).

  • Going forward, we don't want to be in the business of hardcoding version numbers and paths in your project file, so the second case represents the future mechanism for consuming build targets. This case points to a per-VS-version shim file which contains nothing but an import of the "real" build targets file. For VS 2013 (aka version 12.0), $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets is 3 lines, simply importing the real targets at $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets. With this approach, your project file does not need to be updated with new versions and paths when a new VS version comes out. Your project will automatically find the shim file matching whatever version of VS you are using, and that shim file will point to the appropriate "real" targets that are supported in that version of VS.

That's all super neato, except the 3.1.1 installer mistakenly puts the shim file in the "only deploy when VS is detected" bucket. So on a clean build server, although the "real" targets file is installed (it's in the "always deploy" bucket), the shim is not, and you get busted builds with the default templates. :-(

Workaround

It's perfectly valid to edit your project file so that it consumes the real targets directly. This will work. But I would say the preferred workaround is to just create the shim file yourself.

To create the missing shim file (2 files actually, there's also a portable one) yourself, just run below from admin powershell prompt:

$shimDir = "${env:ProgramFiles(x86)}\MSBuild\Microsoft\VisualStudio\v12.0\FSharp"
mkdir $shimDir | out-null

$shimFormat = @'
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft{0}.FSharp.targets" />
</Project>
'@

'','.Portable' |%{ ($shimFormat -f $_ -split '\n') | out-file "$shimDir\Microsoft$_.FSharp.targets" -encoding ascii }

I'm requiring 3.1 in my projects, so I remove that choose block and simply add an FSharpTargetsPath property near the top, see SourceLink.fsproj. It is a simpler version of what we did for the FAKE projects like FakeLib.fsproj.

<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.1\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>

Tags:

Msbuild

F#

F# 3.0