the .NET build system is a nightmare
20230122
these tutorials are supported by viewers like you over at patreon and ko-fi.
a little while ago i published FNAECSTemplate, a template project for making video games with FNA, MoonTools.ECS, and FontStashSharp. i made this because the .NET build system is a hell nightmare from which i cannot awake. i hate it when tutorials rely on you downloading some template project without explaining how you could make it yourself, so this is my general purpose explanation of all the parts of FNAECSTemplate.
top level
in the top level folder, we have our .gitignore and .gitmodules files. .gitignore should be familiar to anyone who knows how to use git (and if you don’t know how to use git, i cannot teach you, we’d be here all day), but you may wonder why we’re using git submodules instead of C#’s builtin package manager, NuGet. the answer is that NuGet sucks and so a lot of people don’t distribute packages on it, including FNA. managing dependencies is much more straightforward and transparent with git submodules than with NuGet.
FNAECSTemplate.sln is a visual studio solution file. more like visual studio PROBLEM files though, am i right??? despite being plain text files, solution files are not really meant to be read or written by humans. the .NET SDK comes with a command line utility, dotnet
, which allows us to create and modify sln files in a way that won’t make the computer yell at us. all the information about this is here. i created a new sln file, and then added the .csproj files from each of the git modules to it. you do not strictly speaking have to do this if you only want to use vscode or some other text editor, but big boy visual studio requires solution files to work, and you will probably want to use the visual studio debugger or profiler at some point.
finally, there’s setup.sh. this is a bash script based on darkerbit’s FNA setup script. it’s in bash instead of something else becuase bash makes it much easier to download files than windows batch files or a python script or something.
mkdir -p libs
cd libs
echo -e "\e[32mDownloading fnalibs...\e[m"
curl -O https://fna.flibitijibibo.com/archive/fnalibs.tar.bz2
echo -e "\e[32mExtracting fnalibs...\e[m"
tar -xf fnalibs.tar.bz2
first, we use curl to download the fnalibs from ethan lee’s wesbite. the FNA libs are the binary dependencies of FNA. they are:
- FAudio, an audio playback library that’s an open-source reimplementation of microsoft’s XAudio
- FNA3D, the rendering backend of FNA
- libtheorafile, a video playback library
- SDL2, the cross-platform window creation and input handling library for FNA (sdl2 can do a lot more than that, but that’s what FNA uses it for)
cd ..
git submodule update --init --recursive
next, we move back up into the top level directory and call git submodule update
to download all the git modules the project depends on. --init
tells your local git repository about the git modules for reasons outlined in this stackoverflow post. --recursive
means to also download the git modules our git modules depend on; FNA includes a number of submodules we need to include for the proejct to work.
echo -e "What is your project name?"
read name
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate/FNAECSTemplate.csproj
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate.sln
sed -i -e "s/FNAECSTemplate/${name}/g" .vscode/launch.json
sed -i -e "s/FNAECSTemplate/${name}/g" .vscode/tasks.json
sed -i -e "s/Game1/${name}/g" FNAECSTemplate/Game1.cs
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate/Game1.cs
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate/Systems/ExampleSystem.cs
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate/Components/ExampleComponent.cs
sed -i -e "s/FNAECSTemplate/${name}/g" FNAECSTemplate/Renderers/ExampleRenderer.cs
mv "FNAECSTemplate.sln" "${name}.sln"
mv "FNAECSTemplate/FNAECSTemplate.csproj" "FNAECSTemplate/${name}.csproj"
mv "FNAECSTemplate/Game1.cs" "FNAECSTemplate/${name}.cs"
mv "FNAECSTemplate" "${name}"
echo -e "\e[32mDone!\e[m"
finally, we use sed
and mv
to rename all the files and folders and their contents to whatever project name the user chose. sed
is the Stream EDitor which is an incredibly powerful command line utility for manipulating text files. here we’re using several regular expressions to replace (which is the s
before the first slash for some reason) globally (the more sensible g
after the last slash) ever instance of the thing between the first two slashes (“FNAECSTemplate”, for instance) with the thing beteween the second two slashes (our project name). mv
just moves the file from the source (first argument) to the destination (second argument).
FNAECSTemplate/
inside the project folder, there are a few files of significance to us. the most important is FNAECSTEmplate.csproj
. this is a C# project file, an XML file that specifies details about the project. the csproj file specification is bizarre and complicated, and csproj files generated by visual studio will generally be completely fucking inscrutable. i wrote this one by hand, and i recommend you do the same for your csproj files.
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
<ProjectReference Include="..\MoonTools.ECS\MoonTools.ECS.csproj" />
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.Core.csproj"/>
</ItemGroup>
the first thing we see inside the root element of the document is an ItemGroup
(which is a generic container for stuff) containing a bunch of ProjectReference
s. these point to the csproj files in all our dependencies, and tell the C# compiler that we can use all the namespaces from those projects.
<ItemGroup>
<None Include="*.targets" />
</ItemGroup>
another item group that points to the .targets files we have in this same folder, we’ll talk about those in a second.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
</PropertyGroup>
here are our project properties. OutputType
tells the compiler we’re building an executable file and not a DLL. TargetFramework
specifies the version of .NET we’re targeting. Nullable
enables or disables the “nullable aware context,” which adds a bunch of language features to help deal with the possibilty of null values. you can read all about it here. Platforms
tells us whether we’re building for x64 or x86, etc.
<Import Project="$(SolutionDir)NativeAOT_Console.targets" Condition="Exists('$(SolutionDir)NativeAOT_Console.targets')" />
<Import Project="CopyLibs.targets" />
<Import Project="CopyContent.targets" />
NativeAOT is a way for you to use C# to build ahead-of-time compiled native code binaries instead of using the .NET virtual machine. for our purposes, this is mainly useful for if we are building for video game consoles. we don’t have a file called NativeAOT_Console.targets, but we could create one, and if we did it would store information used in this process.
ok, now let’s talk about the other .targets files.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Content Include=".\Content\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
this is a lot of crap to say “copy the Content directory and all its contents to the output directory when you build the project.” PreserveNewest
means to copy over content only if it’s newer than the content already in the build directory, which speeds up build times.
the other targets file is similar:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition=" '$(OS)' == 'Windows_NT' ">
<Content Include="..\libs\x64\*.dll">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(OS)' != 'Windows_NT' ">
<Content Include="..\libs\lib64\*.*">
<Link>lib64\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
this one contains a Condition to copy the windows .dll files if we’re building on windows, and the linux libraries if we’re building on linux.
conclusion
and that’s it. hopefully this should be useful to you if you want to create your own template files or modify mine.