GitHub Actions: Build a Xamarin.Forms project and create the APK
09-06-2020In this post I'll show a way to configure a basic GitHub Action workflow in order to build a Xaramin.Forms application and create the APK, only Android at the moment.
First of all, the workflow:
name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
# Build App
build:
runs-on: windows-latest
env:
SERVICE_APP_KEY: ${{ secrets.SERVICE_APP_KEY }}
steps:
- uses: actions/checkout@v2
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.0.0
- name: Build Solution
run: msbuild ./MyApplication.sln /restore /p:Configuration=Release
- name: Create and Sign the APK
run: msbuild MyApplication\MyApplication.Android\MyApplication.Android.csproj /t:SignAndroidPackage /p:Configuration=Release /p:OutputPath=bin\Release\
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: MyApplication.apk
path: MyApplication\MyApplication.Android\bin\Release\com.companyname.myapplication-Signed.apk
Step by step
The first lines are self-explanatory so I'll jump to the build section.
build:
runs-on: windows-latest
This will configure a Windows machine and all steps for this job will run in this environment. The default shell will be a PowerShell, so we can run scripts in this language.
env:
SERVICE_APP_KEY: ${{ secrets.SERVICE_APP_KEY }}
With this line we create an environment variable, SERVICE_APP_KEY with the content of the GitHub secret secrets.SERVICE_APP_KEY. The variables created in an upper level can be accessed by its children elements but a variable created in a step level, for example, cannot be accessed from other step or a parent element.
If you try to print the value of a secret during the build process you will only see something like: SERVICE_APP_KEY: ****
Now let's explain the steps.
steps:
- uses: actions/checkout@v2
The first step is the checkout. This action will download all the code to the current machine.
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.0.0
The next steps is to use the action microsoft/setup-msbuild@v1.0.0 in order to configure the environment that allow us to use the msbuild tool.
- name: Build Solution
run: msbuild ./MyApplication.sln /restore /p:Configuration=Release
Build the solution in Release mode.
- name: Create and Sign the APK
run: msbuild MyApplication\MyApplication.Android\MyApplication.Android.csproj /t:SignAndroidPackage /p:Configuration=Release /p:OutputPath=bin\Release\
In order to create and sign the package we will execute the msbuild command over the Android project with the target /t:SignAndroidPackage. The configuration should be Release and the generated .apk file will be located in {project_folder}\bin\Release
The name of the file will be com.companyname.myapplication-Signed.apk. The default name of the package is com.companyname.myapplication, you can change it in the AndroidManifest.xml file or in the properties of the Android project in Visual Studio.
This process attach the -Signed suffix to the name.
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: MyApplication.apk
path: MyApplication\MyApplication.Android\bin\Release\com.companyname.myapplication-Signed.apk
The last step is to make available the artifact to be downloaded. We will use the action actions/upload-artifact@v2 with the parameters name as the name of the artifact, and the path, the path where the package is located.
How to manage the secret keys
The hardcoded keys are not a good practice ;) A better solution could be create an environment variable in your local system, but, how to access from code? One approach I follow is to create a class to manage the secrets and populate it with placeholders, something like:
public static class Secrets{
public static string ServiceID => "#KEY1#"
public static string OtherKey => "#KEY2#"
}
To replace the placeholders by the valid keys I have a PowerShell script that runs in the pre-build event on Visual Studio.
function Replace-Text{
<#
.SYNOPSIS
Replace the $placeholder value by $key value in $file
.PARAMETER $file
File to replace values. Mandatory
.PARAMETER $placeholder
Value to be replaced. Mandatory
.PARAMETER $key
The new value to replace the $placeholder. Mandatory
.EXAMPLE
Replace-Text file_path "#TEXT TO REPLACE#" "KEY VALUE"
#>
Param(
[Parameter(Mandatory=$true)]
[string]$file,
[Parameter(Mandatory=$true)]
[string]$placeholder,
[Parameter(Mandatory=$true)]
[string]$key
)
((Get-Content -path $file -Raw) -replace $placeholder, $key) | Set-Content -Path $file
}
$file = $args[0]
$placeholder = $args[1]
$key_value = $args[2]
Replace-Text $file $placeholder $key_value
This script have three parameters:
- the $file where the placeholder are
- the $placeholder to be replaced
- the $key_value from the environment variable
This script is called from the Pre-build event command line. Project properties -> Build Events:
powershell.exe -ExecutionPolicy Unrestricted $(ProjectDir)..\..\Tools\replace-keys-script.ps1 $(ProjectDir)Secrets.cs "`#KEY1`#" $env:SERVICE_APP_KEY
powershell.exe -ExecutionPolicy Unrestricted $(ProjectDir)..\..\Tools\replace-keys-script.ps1 $(ProjectDir)Secrets.cs "`#KEY2`#" $env:OTHER_KEY
Let me show the folder structure:
/
|- Solution.sln
|- Project/
|- Project.Android/
|- Tools/
|- replace-keys-script.ps1
Why to use $(ProjectDir)..\..\Tools instead of $(SolutionDir)Tools? Because $(SolutionDir) does not works on GitHub Actions for wharever reason.
As you may notice, this process change the content of the files and Git wil mark them. To fix this I do a checkout for all files in the Post-build event:
git checkout $(ProjectDir)Secrets.cs
After the build process the application will work with correct values and the code remains intact.
The only thing we need to do in GitHub is to create the secrets we need for the keys and create the environment variables in the workflow with the same name as we have in out system.
COMMENTS