However, as soon as you try to setup a CI/CD pipeline following the guidance of the previous post for your React Native project which uses native modules for Windows, you hit a blocker. The Visual Studio build task will fail with a series of errors like the following ones:
[error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\AttributedViewManager.cs(447,21): Error CS8107: Feature 'default literal' is not available in C# 7.0. Please use language version 7.1 or greater. [error]node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\JSValue.cs(105,36): Error CS8107: Feature 'readonly references' is not available in C# 7.0. Please use language version 7.2 or greater.
Shall we give up in our plan of using Azure DevOps to build a CI/CD pipeline for this scenario? Absolutely not! Self-hosted agents to the rescue =)
For scenarios like this, where the available hosted agents don't meet our requirements, Azure DevOps supports the concept of self-hosted agents. A self-hosted agent is nothing more than a regular machine, where you deploy an agent which is able to receive tasks from Azure DevOps and execute them. The agent is an application that can run in interactive mode or simply be installed as a service, so that it can performs all the operations in background. The advantage of a self-hosted agent is its flexibility: being a regular machine, you can install everything you need to satisfy your requirements: development tools, SDKs, frameworks, etc. This helps also to reduce the execution times of a pipeline. Sometimes, some of our requirements can be satisfied also on hosted agents with installation tasks: for example, in the previous post we have learned how to use Chocolatey to install the Windows 10 1903 SDK, which is missing on the vs2017-win2016 agent. However, all these tasks must be added on top of the compilation time, making the whole execution longer.
The downside of a self-hosted agent is that you need to maintain it. You need to keep it up & running, you need to patch it, you need to pay for it, etc. Hosted agents, instead, are maintained directly by Microsoft. The difference between a hosted agent and a self-hosted agent is not very different between choosing a IaaS (Infrastructure as a Service) or PaaS (Platform as a Service) approach for running an application in the cloud.
Setting up a self-hosted agent is a quite straightforward operation. First, you need to have a machine that you want to use as an agent. It can be any kind of machine: physical, virtual, in the cloud. As long as it's connected to Internet, you're good to go. Of course, the best approach is to use a dedicated virtual machine, where you're going to install only the tools you need to perform the compilation. In my case, I opted for creating a dedicated VM on Azure. If you have a Visual Studio subscription linked to your Azure account, you can choose one of the available Windows 10 Pro images (in my case, I chose the most recent version, Windows 10 1909). Otherwise, you can choose a Windows Server 2019 image as well.
As size, keep in mind that disk speed is very important. React Native for Windows is built on top of C++ and, as such, the compilation times are quite long. If you choose a cheap storage (like a regular HDD instead of SSD), the compilation time will be very long. My suggestion is to go, at least, with a DS2_V2, which is the same size leveraged by the Microsoft hosted agents.
Once your VM is up & running, regardless if you have created it locally or in the cloud, you will need to setup the agent.
I won't go into all the details on how to setup a self-hosted agent, since my colleague Freist has explained it in a great way in the following blog post. Also the official documentation does a great job in explaining all the required steps.
The process is really easy. You just need to download a package from the Azure DevOps website, copy it over on your machine and run a script to configure it. The script will ask you a few questions, like the security token to connect your Azure DevOps instance, the work folder, if you want to install the agent as a Windows Service, etc. Once the operation is complete, you're all set. The agent will take care of communicating with Azure DevOps via HTTPS, to exchange information: it will receive the tasks to perform and it will send back the produced artifacts.
Once the agent is up & running, you can start installing all the tools you need to compile our React Native for Windows project. Let's see them.
We have already talked about Chocolatey in the previous post. It's a popular package manager for Windows and it's the easiest way to install software on a machine (especially a build agent), since everything can be done via command-line, without requiring any user interaction. To install it, you just need to open an administrative PowerShell prompt on the VM (the easiest way is to right click on the Start button and choose Windows PowerShell (Admin)) and launch the following command:
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
Once the tool is installed, you'll be able to install applications just by launching
choco install followed by the unique identifier of the package. You can explore all the available packages on the official website.
You will need Node.js installed on the machine, since it's leveraged by React Native to handle all the modules. Now that we have Chocolatey on the machine, installing Node.js is really easy. Open a PowerShell prompt with administrative rights and run the following command:
choco install nodejs.install --version=12.9.1
Make sure to specify the
--version property, since 12.9.1 is the Node.js version which works best with React Native at the time of writing. Different versions can lead to errors during the bundle generation.
When you commit your React Native project to your repository, by default the node_modules folder is ignored. These are libraries and tools which haven't been developed by us, so it wouldn't make sense to waste space on our repository for stuff that can be easily downloaded. As such, you will have to restore all the dependencies before running the compilation. The best way to do is to leverage Yarn, a popular package manager for Node. Also Yarn is available on Chocolatey, so you can just run the following command from a PowerShell prompt with administrative rights:
choco install yarn
During the compilation of the package, at some point Visual Studio will launch the
Regardless of your choice, to change this behavior open the Start menu and type:
You will open a window that will list all the services installed in the system. Look for the agent service, which will be called Azure Pipelines Agent, followed by the a string composed by:
For example, in my case it's called Azure Pipelines Agent (mpagani-ms.Default.VSPreviewBuild).
Now double click on it to open its properties and move to the Log on section. Select the This account option and, with the Browse button, search for the user on the machine you have decide to use to run the service. In my case, I chose to leverage my current user, which is called qmatteoq and he has administrator rights. You will need to provide also the password of the account.
Once you have setup the user who runs the agent, you're ready to install the React Native CLI. Open an administrative prompt and run first the following command:
npm config set prefix C:\Users\qmatteoq\AppData\Roaming\npm
Make sure to replace qmatteoq with the name of the user you have chosen to run the service. This command will set the default folder which will be used as cache for NPM packages. Now you can install the CLI with the following command:
npm install -g react-native-cli
That's it. This was the last requirement to install on the machine. Now we can go back to the Azure DevOps portal and create our pipeline.
The process of creating a CI/CD pipeline isn't very different from the one we did in the previous post. In your Azure DevOps project move to the Pipelines section, create a new pipeline, choose where your source code is hosted and, in the end, choose Universal Windows Platform as last template. We will need, also in this case, to make a few tweaks, but we will have to perform fewer steps. In the previous post, in fact, we were leveraging a hosted agent, so we needed to install every time the missing dependencies, like the Windows 10 1903 SDK or the React Native CLI. In our scenario, instead, we have already installed all the dependencies on the machine, so we don't need to install them every time we perform a new build.
Here is how your YAML file should look like:
# Universal Windows Platform # Build a Universal Windows Platform project using Visual Studio. # Add steps that test and distribute an app, save build artifacts, and more: # https://aka.ms/yaml trigger: - master pool: Default variables: solution: 'windows/*.sln' buildPlatform: 'x64' buildConfiguration: 'Release' appxPackageDir: '$(build.artifactStagingDirectory)\AppxPackages\\' name: $(date:yyyy).$(Month)$(rev:.r).0 steps: - task: VersionAPPX@2 displayName: 'Version MSIX' inputs: Path: '$(Build.SourcesDirectory)' VersionNumber: '$(Build.BuildNumber)' InjectVersion: true - script: yarn install - task: NuGetCommand@2 inputs: command: 'restore' restoreSolution: 'windows/*.sln' feedsToUse: 'select' - task: VSBuild@1 inputs: solution: '$(solution)' msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Never /p:UapAppxPackageBuildMode=SideloadOnly /p:AppxPackageSigningEnabled=false' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(appxPackageDir)' ArtifactName: 'drop'
Let's see which are the major differences compared to the one we have created in the previous post:
Default. If you have setup the agent on the machine using the default settings, it will belong to the Default pool. With this setting we're telling to Azure DevOps that we don't want to use a hosted agent, but our custom one.
The rest of the steps are the same:
VersionAPPXtask, we inject it in the manifest of your application.
That's it! Now we have a CI pipeline that can automatically build a new MSIX package every time we commit some code to the repository of our React Native project which uses one or more native modules for Windows. Now we can build a release pipeline, which will take care of deploying the MSIX package we have just created to our users: we can upload it on the Microsoft Store, we can upload it on a website or cloud storage to make it available through sideloading, etc. You can learn more about this in the last chapter of my latest e-book titled MSIX Succinctly, which has been published and released for free by Syncfusion, or in Exercise 6 of the Windows application modernization workshop built by my team.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.