Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitVersionTask does not work correctly with fsharp projects building an exe. #1831

Closed
da9l opened this issue Sep 27, 2019 · 18 comments · Fixed by #1896
Closed

GitVersionTask does not work correctly with fsharp projects building an exe. #1831

da9l opened this issue Sep 27, 2019 · 18 comments · Fixed by #1896

Comments

@da9l
Copy link
Contributor

da9l commented Sep 27, 2019

When adding the GitVersionTask to a fsharp fsproj that build an exe one get this error message when running dotnet build

C:\Users\...\repos\reproducegvdn\Program.fs(3,1): error FS0222: Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration. [C:\Users\...\repos\reproducegvdn\reproducegvdn.fsproj]

My best guess is that the generated GitVersionInformation.fs file is placed last in the list of files to compile. The Program.fs containing the decorated main function must always be last. See below for details.

Shortest steps to reproduce:

md reproducebug
cd reproducebug
dotnet new console -lang f#
dotnet add package gitversiontask
git init
git add -A
git commit -m "Initial commit"
dotnet build

If one run with dotnet build --verbosity detailed one can find the following input to the fsc compiler (redacted for clarity). Notice the order of the files in the end.

C:\Program Files\dotnet\dotnet.exe "C:\Program Files\dotnet\sdk\2.2.300\FSharp\fsc.exe" -o:obj\Debug\netcoreapp2.2\reproducegvdn.dll
         -g
         --debug:portable
         --noframework
         --define:TRACE
         --define:DEBUG
         --define:NETCOREAPP
         --define:NETCOREAPP2_2
         --optimize-
         --tailcalls-
         ...
         --target:exe
         --warn:3
         --warnaserror:3239,76
         --fullpaths
         --flaterrors
         --highentropyva-
         --targetprofile:netcore
         --nocopyfsharpcore
         --simpleresolution
         C:\Users\...\AppData\Local\Temp\.NETCoreApp,Version=v2.2.AssemblyAttributes.fs
         obj\Debug\netcoreapp2.2\reproducegvdn.AssemblyInfo.fs
         Program.fs
         obj\Debug\netcoreapp2.2\AssemblyInfo_reproducegvdn_da4sk0i1.nf4.g.fs
         obj\Debug\netcoreapp2.2\GitVersionInformation_reproducegvdn_woodujve.ebf.g.fs

Same error with both SDK 2.2.300 and 3.0.100

@da9l
Copy link
Contributor Author

da9l commented Oct 15, 2019

I might be missing something here but I think..

What is needed here is that the gitversion__.fs is placed first in the list of fs files to compile. F# Does not like any file to exist after the Program.fs file.

It doesn't make sense to place it in the project file but how could one make it appear first in the list of files to be sent to the compiler?

@da9l
Copy link
Contributor Author

da9l commented Oct 31, 2019

Yay, I found the cause of the problem and a workaround!
in GitVersionTask.targets UpdateAssemblyInfo and GenerateGitVersionInformation do this:

<ItemGroup>
     <Compile Include="$(AssemblyInfoTempFilePath)" />
</ItemGroup>

<!-- and -->
<ItemGroup>
    <Compile Include="$(GitVersionInformationFilePath)" />
    <FileWrites Include="$(GitVersionInformationFilePath)" />
</ItemGroup>

This appends these two files to the list of files to be compiled. Since F# requires that the [<EntryPoint>] marked expression (in this case in Program.fs) is the last one that is compiled this build will break.

Workaround
We found a workaround: Put the Include Program.fs into a task that is guaranteed to be executed after GitVersionInformationFilePath and UpdateAssemblyInfo. Remove the existing Program.fs inclusion and include it again.
This makes Program.fs to end up as the last file to be compiled.
This seems to be working ok in Visual Studio 2019 but perhaps not in Visual Studio Code (it doesn't show Program.fs in the right place)

  <ItemGroup>
    <Compile Include="SomeFunctions.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>

  <Target Name="GitVersionInfoBeforeEntryPointFileWorkaround"
          AfterTargets="GenerateGitVersionInformation;UpdateAssemblyInfo"
          DependsOnTargets="GenerateGitVersionInformation;UpdateAssemblyInfo">
    <ItemGroup>
      <Compile Remove="Program.fs" />
      <Compile Include="Program.fs" />
    </ItemGroup>
  </Target>

@asbjornu
Copy link
Member

That's great, @da9l! Is there something that can be adjusted in GitVersion to make this experience better out of the box? If you have ideas, we would love a PR.

@da9l
Copy link
Contributor Author

da9l commented Nov 1, 2019 via email

@da9l
Copy link
Contributor Author

da9l commented Nov 1, 2019 via email

@inosik
Copy link

inosik commented Nov 3, 2019

Hey, I've seen this issue on Twitter and thought I'd chime in.

The .NET SDK generates an assembly info file for F# projects by default, too, but it doesn't add the newly generated file as an Compile item, but as an CompileBefore item. MSBuild then turns these to Compile items and prepends them to the users files. This package should do that as well for F# projects.

@da9l
Copy link
Contributor Author

da9l commented Nov 4, 2019

Wow! I really didn't know that. Thanks!
@inosik, do you have a link to any documentation on CompileBefore? I don't get any hit on that in the official ms docs: https://docs.microsoft.com/en-us/search/?search=CompileBefore&category=All
I'm assuming that I'm looking in the wrong place.

@da9l
Copy link
Contributor Author

da9l commented Nov 4, 2019

@asbjornu do you think it would be a good idea to always add the gitversion generated files as CompileBefore? Regardless of the language?

@da9l
Copy link
Contributor Author

da9l commented Nov 4, 2019

Ok, some searching tells me this is a F# thing.
https://github.com/dotnet/fsharp/blob/master/src/fsharp/FSharp.Build/Microsoft.FSharp.Targets
I will try to create a PR with a conditional on the inclusion. Have no clue on how to write a test for it tho.

@da9l
Copy link
Contributor Author

da9l commented Nov 5, 2019

For anyone following this, I've made some progress but it's not complete.
https://github.com/da9l/GitVersion/tree/feature/EnableGitVersionTaskForFSharpExeProjects

I've made the CompileBefore Include conditional wrt the $(Language) property. It still works for C# projects but I'm not getting any version info at all into the F# assemblies.

I'm testing it in this repo: https://github.com/da9l/testgitversiontask4fsexe

@da9l
Copy link
Contributor Author

da9l commented Nov 6, 2019

Blergh. I've managed to make dotnet build on fsproj to behave nicely when gitversiontask is included. nuget packages are being generated with nice versions.

I'm however unable to get version info into the assemblies. Doing some searching this seems to be a problem with the F# dotnet core compiler that it can't make this work atm. dotnet/fsharp#7220

It should work with Visual Studio 2019.
My goal is to complete the PR anyway and let it be up to the maintainer here to make the call.

@da9l
Copy link
Contributor Author

da9l commented Nov 6, 2019

Ok. Verified. Assembly info gets into the assemblies when compiling in in VS2019. But not in dotnet cli.

VS2019:
image

dotnet build:
image

@inosik
Copy link

inosik commented Nov 6, 2019

Keep in mind that this dialog doesn't show the actual attribute values. .NET assemblies carry some Windows specific resources, which is what the "Properties" dialog shows.

You can check the assembly with the IL Disassembler. If it has the AssemblyVersionAttribute and friends set to the correct values, we're good.

@asbjornu
Copy link
Member

asbjornu commented Nov 6, 2019

Perhaps the presence of AssemblyVersionAttribute and friends is something we can add a test for by producing an assembly with GitVersion, then loading that assembly and reading its version number?

@da9l
Copy link
Contributor Author

da9l commented Nov 11, 2019

@inosik I tried to view the dll using ildasm and it shows

.assembly fconsole
{
  .ver 0:1:0:0
}

Don't know if that counts.

@da9l
Copy link
Contributor Author

da9l commented Nov 11, 2019

Note to anyone looking for this:
As mentioned before the version resource info, visible when displaying file properties on an assembly in windows, is not created when running
dotnet build
but when building with VS or classic msbuild it works
msbuild <yoursln.sln>

@inosik
Copy link

inosik commented Nov 11, 2019

Yes, almost. This version is the value of the AssemblyVersion attribute. In your screenshot of the "Properties" dialog above, the value of "File version" is AssemblyFileVersion, and "Product version" is AssemblyInformationalVersion.

What I meant was this:

ildasm_2019-11-11_12-05-42

@da9l
Copy link
Contributor Author

da9l commented Nov 11, 2019

Thanks!
I got this result that match the output of cli gitversion.

.custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 30 2E 31 2E 30 2E 30 00 00 )             // ...0.1.0.0..
.custom instance void [System.Runtime]System.Reflection.AssemblyVersionAttribute::.ctor(string) = ( 01 00 07 30 2E 31 2E 30 2E 30 00 00 )             // ...0.1.0.0..
.custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 42 30 2E 31 2E 30 2B 34 2E 42 72 61 6E 63   // ..B0.1.0+4.Branc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants