How to compile a single .cs file ?
If you look at the content of what visual studio creates, you’ll find multiple files, one .sln for your solution, one .csproj for your first project, and one .cs for your code.
Alternatively, one can create a project using dotnet CLI commands, dotnet new, then build using dotnet build and run usin dotnet exec :
$ dotnet new console -n MyConsole
$ dotnet build MyConsole.csproj
$ dotnet exec obj/bin/MyConsole.dll
You’ll notice that the CLI doesn’t provide a command to “compile” a single .cs file, either build the whole project or the whole solution.
The question arise, what does dotnet build do under the hood and how to compile a single .cs file without a .csproj ?
How does it work under the hood
$ /usr/lib/dotnet/dotnet exec "/usr/lib/dotnet/sdk/8.0.105/Roslyn/bincore/csc.dll" ... /reference:/usr/lib/dotnet/packs/Microsoft.NETCore.App.Ref/8.0.5/ref/net8.0/Microsoft.VisualBasic.Core.dll ... Program.cs ...
This behemoth of a line is what compiles our simple .cs file, let’s have a look at the essence of the command, you can find more details on the csc options here : https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/
- /reference : as the name suggests, those are the references needed to compile our code, we have a lot of references for a simple Console.Writeline file but this command line was copied from a dotnet build command, in the logs we see that the target responsible for adding references is named : ResolveTargetingPackAssets, will dive in this another day.
- /out : set the output file name, by default it will take the name of the file and change the extension to .exe, the extension doesn’t change how the file will be run, a .exe file doesn't mean it's native binary.
Then we have those four .cs files to be compiled :
- Program.cs
- obj/Debug/net8.0/MyConsole.GlobalUsings.g.cs
- "obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs"
- obj/Debug/net8.0/MyConsole.AssemblyInfo.cs
We don’t need AssemblyAttributes nor AssemblyInfo, GlobalUsings were introduced in .Net6.0 with C# 10, they allow defining “using” statements without rewriting them in every cs file on the project, those global usings may be needed.
Let’s try the following command and see what we’ll get :
$ /usr/lib/dotnet/dotnet exec
"/usr/lib/dotnet/sdk/8.0.105/Roslyn/bincore/csc.dll" Program.cs
and we get the following exception :
Program.cs(2,1): error CS0518: Predefined type 'System.Object' is not defined or imported
error CS0518: Predefined type 'System.Void' is not defined or imported
error CS0518: Predefined type 'System.String' is not defined or imported
Clearly we are missing some references, we need to add the two references: System.Runtime and Sytem.Console, but we still get an exception:
Program.cs(2,1): error CS0103: The name 'Console' does not exist in the current context
Since we compiled only our base file without global usings, the compiler doesn’t know what Console refers to !
If we take a look at the content of ./obj/Debug/net8.0/MyConsole.GlobalUsings.g.cs, it contains only a list of global usings:
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;
we only need System, let’s create a new .cs alongside our Program.cs to define the global usings :
$ echo "global using global::System;" > GlobalUsing.cs
Alternatively, we can add using System inside out Program.cs.
Then
$ /usr/lib/dotnet/dotnet exec
"/usr/lib/dotnet/sdk/8.0.105/Roslyn/bincore/csc.dll" GlobalUsings.cs
Program.cs
/lib:/usr/lib/dotnet/packs/Microsoft.NETCore.App.Ref/8.0.5/ref/net8.0/
/reference:System.Console.dll /reference:System.Runtime.dll
And there we have our Program.exe alongside out Program.cs, this Program.exe isn’t executable on it own, it still needs the CLR to run this assembly !
$ dotnet exec Program.exe
Aaaaand we’re not done yet, another exception arises:
A fatal error was encountered. The library 'libhostpolicy.so' required to execute the application was not found in '/home/dalkevo/Documents/Learning/dotnet/CsOnly/'.
Failed to run as a self-contained app.
- The application was run as a self-contained app because '/home/dalkevo/Documents/Learning/dotnet/CsOnly/Program.runtimeconfig.json' was not found.
- If this should be a framework-dependent app, add the '/home/dalkevo/Documents/Learning/dotnet/CsOnly/Program.runtimeconfig.json' file and specify the appropriate framework.
So basically, this tells us that dotnet exec tried to run this as a self-contained app, but didn’t find the necessary libraries, however our app is framework dependent, so we need to specify the runtime version …
Program.runtimeconfig.json should look like this :
{
"runtimeOptions": {
"framework": {
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
}
}
}
then :
$ dotnet exec Program.exe
and voilĂ , we have our first output.
In conclusion :
While this may seem a little bit too complex for a simple "Hello World" program, .Net does a lot of work for you to concentrate on the code only without worrying about how to get dependency or how to compile a whole solution with multiple references.
Comments
Post a Comment