There are a multitude of ways on how one can structure a .NET project.
It's no wonder one can also have a multitude of downsides when chosing
one of them. At my $CURRENT_JOB
we introduced Fable to our project and
guess what? We had problems with our testing infrastructure that
required some tweaking to make it happen (I'll not get into the
problem details here 😜).
After some trial and error I got the conclusion that our solution
would get the most out of it if we just had a src
and a tests
directory inside each project. You can picture its structure like
this:
./Solution.sln
./src/API/src/*.fs
./src/API/tests/*.fs
...
However, while this solved most of our previous issues, it also introduced two new problems:
- test code would ship with our release binary
- we have multiple EntryPoints on our solution, how will this work out?
Removing unwanted code from our releases
I assumed that .NET would remove code with "Test" attributes and unused code from the release artifact. Oh boy, was I wrong. This is a bummer because you don't want to ship more information than is needed as one might be able to strip debug symbols or even decompile a bunch of code you may not want others to see.
After some research, I found this Stack Overflow answer showing a way
of disabling this code during release. They even introduced a new way
of structuring tests, how nice! This is how my .fsproj
file looked
as after some testing:
<!-- Project-related group -->
<ItemGroup>
<!-- Project files -->
<Compile Include="src/Program.fs" />
<!-- Project-related dependencies -->
<PackageReference Include="FsToolkit.ErrorHandling" Version="4.4.0" />
</ItemGroup>
<!-- Test-related group -->
<ItemGroup Condition="'$(Configuration)' != 'Release'">
<!-- Test files -->
<Compile Include="tests/Shared.fs" />
<!-- Test dependencies -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*"/>
</ItemGroup>
Now, the only important part here is the attribute
Condition="'$(Configuration)' != 'Release'"
. Basically it's saying
that this ItemGroup
should only be evaluated if we are not
releasing. You don't need to organize your project file like I did
but, personally, I liked how it looked.
Dealing with EntryPoints
You can see that I left the test dependency Microsoft.NET.Test.Sdk
.
It was on purpose, this almost made me have a mental breakdown! I kept
getting the following error:
error FS0433: A function labeled with the 'EntryPointAttribute' attribute must be the last declaration in the last file in the compilation sequence.
No matter how much I messed with the project file, OutputTypes and EntryPoints, I couldn't get this error1 to go away! At this point, removing as much code as possible from the error context helped a lot. Such as that I discovered the problem when I had this single dependency left!
Again, someone may have faced the same issue, right? Yes, someone have! The fix is really simple, just add this attribute on your project file:
<GenerateProgramFile>false</GenerateProgramFile>
-
Being honest here, I'm glad I have setup my project to [error on build warnings]({{< relref "safer-fsharp-environments" >}}).↩