Compiling .NET Core for Windows 10 ARM64


If you take a look at my Twitter, I have many examples of me showing passion for Windows On ARM. Ever since I started using my ASUS NovaGo that I won at the 2018 Microsoft Build Conference (this totally had to be fate), I’ve been obsessed with finding practical uses for a device running an ARM64 version of Windows 10 that escaped beyond the bounds of mimicking the exact activities I would do from my phone and more like a typical computer.

As a developer and someone who loves tinkering with things, I wanted to see what this platform can truly do. Naturally, I did the obvious thing and installed Visual Studio 2019, but was quickly reminded that everything is running through an x86 emulation layer. That meant the act of compiling a .NET Core app was happening from an x86 application. That didn’t make me feel too accomplished. Luckily, I was still able to use .NET Core on ARM64 via WSL – which runs amazingly, but I still wanted to be able to build natively in Windows.

So, what did I do to ease my pain? I figured out a way to compile .NET Core for Windows ARM64!

We’re going to make this happen by splitting the process into two parts:

  1. Building the ASP.NET Core Runtime for ARM64
  2. Building the .NET Core SDK for ARM64


Building the ASP.NET Core Runtime for ARM64

Prerequisites

To get started, you’ll want to head over to the ASP.NET Core GitHub repository and read the Windows section of the Build From Source documentation to install the pre-requisite tools. Make sure you have installed everything it told you to install. I also recommend that you also install the MSVC v142 VS 2019 C++ ARM64 build tools components within the Visual Studio 2019 installer.


Building the Source

Cloning

You can either build from master (if you want the bleeding edge build) or simply build from a release branch. For this post, we’ll be building from the release/3.1 branch. To get started, check out the source and submodules from the AspNetCore repo.


git clone -b release/3.1 -recursive https://github.com/dotnet/aspnetcore

File Modifications

Currently, we have to make a few file modifications in order for the build process to work with Windows ARM64. After a lot of trial and error, the changes aren’t bad at all.


Directory.Build.props

This file contains the common build properties to apply to projects. There’s a section which contains a list of the supported runtime identifiers and happens to exclude the runtime identifier for Windows ARM64. To resolve this, search for <SupportedRuntimeIdentifiers> and add win-arm64 to the list.

src\Framework\src\Microsoft.AspNetCore.App.Runtime.csproj

This is the main project file for the ASP.NET Core Runtime. The line we will change has to do with setting the path to the Crossgen tool package. Without the proper rid mapping in the conditional, cross-compilation won’t occur. To resolve this, search for <CrossgenToolPackagePath and look specifically for linux-arm64 in the Condition logic. This line sets all Linux ARM64 runtime ids to use the x64_arm64 Crossgen tool. Since the same tool applies to Windows ARM64, add OR ‘$(TargetRuntimeIdentifier)’ == ‘win-arm64’ to the condition.

build.ps1

This is the main PowerShell script that handles the compilation of the build from Windows. You typically won’t be calling this directly, but the build.cmd script calls it underneath. This script has a switch that sets the target architecture the build will compile for. Currently it’s locked to x64, x86, and arm. To make this work with Windows ARM64, find the $Architecture line and modify the ValidateSet attribute above it to add , 'arm64' to the list.

eng\Dependencies.props

This file lists all of the external package references the build needs in order for it to run properly. Included in the list, are the runtime packages needed for Crossgen to work. As you can guess, this doesn’t contain a runtime package for Windows ARM64 – more specifically the Microsoft.NetCore.App.Runtime package for the win-arm64 runtime identifier. To make this work, search for microsoft.netcore.app.runtime.win-arm, copy that entire line, paste it below and change the text to microsoft.netcore.app.runtime.win-arm64.

global.json

This file sets global .NET Core settings that apply to the root directory and everything below it. In this file, we’ll need to change the Visual Studio version from 16.0 to 16.3. This resolves some weirdness around a build package failing to detect locations of build tools bundled with Visual Studio. Search for “version” and set the value to the version listed above.

That’s all we need to do for file modifications!


Creating Windows ARM64 Packages

The fun part of this process is actually building the packages. To kick off the build and package creation, open up a command prompt to the root directory of the local AspNetCore repository and run the following command:


build.cmd -Configuration Release -pack -all /p:SkipTests=true


That’s it! It’ll pull down the packages and build everything you need. The compiled ASP.NET Core Runtime and the NuGet packages will exist in the artifacts directory off of the root folder of the cloned repository.

artifacts\packages\Release
This folder contains both the Shipping and Non-Shipping NuGet packages for the ASP.NET Core Runtime components. You'll need to zip the two folders underneath of this directory into an archive to transfer to the target WoA machine.

artifacts\installers
The zip file matching something similar to aspnetcore-runtime-3.1.2-win-arm64.zip depending on the version you're building, contains the actual ASP.NET Core Runtime. Transfer this over to the target WoA machine also.

Building the .NET Core SDK for ARM64

The .NET Core SDK is the super important part of this, as it will allow you to actually create and compile source code. This process is easier than the ASP.NET Core Runtime build and only takes a tiny bit of file modification. After this process is finished, you'll be able to compile on your Windows ARM64 device.


Building the Source

Cloning

Keeping with the same version that the ASP.NET Core Runtime is on, we'll check out the release/3.1.2xx branch.

git clone -b release/3.1.2xx -recursive https://github.com/dotnet/core-sdk


File Modifications

Enter into the root of your cloned copy of the core-sdk repo and make the following file modifications.

src\redist\targets\Crossgen.targets:

A new DIASymReaderCrossGenFilter line needs to be added to target amd64 when the Architecture is arm64 and OSName is win.

src\redist\targets\GenerateBundledVersions.targets:

This file generates a list of the runtime packs and their individual runtime identifiers. MSBuild will check this when attempting to build. To fix any errors when targeting win-arm64 with the ASP.NET Core Runtime, find the <AspNetCore30RuntimePackRids portion in code and add win-arm64 to the list.

src\redist\targets\BundledDotnetTools.targets:

The first line of the file has an ItemGroup condition that only includes the Bundled DotNet Tools (dotnet-watch, dotnet-dev-certs, dotnet-user-secrets) if we're including the ASP.NET Core Runtime in the packaging. Since we're not, let's just remove that condition. This isn't a modification that I would actually commit, because it doesn't make sense to – but it'll be the only way these tools get built. If you don't care about the tools, feel free to skip this.


Creating Windows ARM64 Packages

To build the .NET Core SDK, we're going to run the build command and exclude WinForms and WPF (not supported yet), along with the ASP.NET Core Runtime (we built it already).

build.cmd -configuration Release -architecture arm64 -pack /p:IncludeAspNetCoreRuntime=false /p:IncludeWpfAndWinForms=false

When the build finishes, the output will be within the artifacts directory

artifacts/packages
The file named similar to dotnet-sdk-3.1.200-preview-xxxxxx-win-arm64.zip is the .NET Core SDK. Transfer this over to the target WoA machine.

Installing Everything on Windows ARM64

If you've made it this far, congratulations! How do we get this running on a Windows on ARM machine? Every step here assumes that you're doing this from that particular machine:

  1. Create a dotnet folder under C:\Program Files.
  2. Add C:\Program Files\dotnet to the Path variable in System Environment Variables. Make sure it's above C:\Program Files (x86)\dotnet, otherwise the ARM64 SDKs and Runtimes won't be detected.
  3. Extract the contents of the .NET Core SDK zip into the dotnet folder.
  4. Copy the shared\Microsoft.AspNetCore.App folder from the ASP.NET Core SDK zip into the dotnet\shared folder.
  5. Download the latest NuGet CLI and put it in C:\Windows\System32.
  6. Extract the contents of the ASP.NET Core Runtime NuGet packages zip somewhere (c:\extractednugetfolder as an example) and run the following commands from an elevated command prompt:

nuget init c:\extractednugetfolder\NonShipping %USERPROFILE%\.nuget\packages -expand
nuget init c:\extractednugetfolder\Shipping %USERPROFILE%\.nuget\packages -expand

After running through these steps, you should have a fully functional .NET Core SDK (minus the desktop parts) and an ASP.NET Core Runtime made to target Windows 10 ARM64!


Remarks

I'm happy to see the .NET Core SDK running properly on my Surface Pro X. The trial/error  along with my persistence actually helped me to understand how intricate this build process can be. There's still so much to dig into and understand, but the support is there. All of the contributors to these projects have made this happen. I'm continually looking forward to more components having support for Windows on ARM due to its potential. If we can show the power of this platform now, then it's possible that more investment will be done to improving its efficiency and performance. My wish is for more ARM64 devices to be provided to the right teams that need them, so it can actually have a fighting chance – if not, I'll be more than happy to test for anyone .