NativeAOT Development



This post is written to document the process of development runtime or library features for NativeAOT. This is NOT meant for NativeAOT compiling your application.

Hello Native AOT

To begin with, we will start with building a HelloWorld application and then NativeAOT compile it. The application itself is unimportant, we can simply use dotnet new console to create a HelloWorld application.

Next, we add the following MSBuild property to the project file:

<PublishAot>true</PublishAot>

This will enable the NativeAOT compilation during publish. We can then publish the application using the following command:

dotnet publish

This will generate a native executable in the publish folder, the problem is that it will be using whatever version of .NET you are targetting and installed.

For our purpose, we would like to use our own build of .NET. To do that, we can add a specific reference to the ILCompiler nuget package on the dotnet 8 transport feed as follow:

<ItemGroup>
    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="*-*"/>
</ItemGroup>

And also make the build system aware of the dotnet8-transport feed through the nuget.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
    <clear />
    <add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />
    <add key="nuget" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>

Now the build will try to use the latest build it can find on the transport feed and hopefully this will be recent enough for the development.

Determine the build

The next step is to determine the build we are using, this is to synchronize our development so that the change is based on top of the right commit.

To do that, clean the bin and obj folder to force native compilation to happen. Then publish the app with the logging option as follow:

dotnet publish -bl

This will generate a file named msbuild.binlog, then we can inspect the log using the MSBuild Binary and Structured Log Viewer. In particular, search for the IlcCompile target and look for the CommandLineArguments of the Exec task. That should show you the path to the Ilc executable, while should include the version number.

Determining the commit hash

The version number is not enough, we need the commit hash so that we can synchronize the build. The easiest way to determine the commit hash is to look at the Ilc executable itself. On Windows, we can use the file explorer and right click to see the detail tab to find it. For Linux, we can use the strings command on the executable to find it.

Synchronize the build

Once we have the commit hash, we can now synchronize our runtime repo clone to that commit hash, apply the desired changes, and build the runtime repo as usual. NativeAOT binaries are built as part of the CoreCLR subset.

Use the build

As we can see, the Exec task of the IlcCompile target contains the Ilc command line. It references a rsp file. We can change the RSP file to point to our locally built binaries. Similarly, the Exec task of the LinkNative target contains the Link command line, and we can just as easily change that target. With that, we can run those command lines and generate the final binary using our locally build NativeAOT binaries as we needed.