Reputation: 15684
I am a bit shocked Linux does not support building .NET MAUI apps unless they are
android .NET SDK Workload for building Android applications.
macos .NET SDK Workload for building macOS applications.
maui-android .NET MAUI SDK for Android
maui-tizen .NET MAUI SDK for Tizen
maui-windows .NET MAUI SDK for Windows
runtimes-windows Windows Runtime Packs
runtimes-windows-net6 Windows Runtime Packs
wasm-experimental .NET WebAssembly experimental tooling
wasm-tools .NET WebAssembly build tools
wasm-tools-net6 .NET WebAssembly build tools for net6.0
In any case, I'd like to know how do .NET Maui developers automate CI/CD in GitLab. Is there any built-in mechanism like there is in GitHub or Azure Pipelines? Or does it have to be configured manually? Could I see an example?
This is what I've been trying so far, considering that the base image mcr.microsoft.com/dotnet/sdk:7.0
won't be able to be used for certain platforms unfortunately..
I have created a custom Docker image. Let's call it dotnet-maui
FROM mcr.microsoft.com/dotnet/sdk:7.0
# Install workload maui
RUN dotnet workload search
RUN dotnet workload install android maui-android --ignore-failed-sources
Is there any better alternative? Maybe an image that uses MacOs or windows where MAUI is supported?
And now I use that image as the GitLab base image for my MAUI apps. Specifically for one that targets android only, but I am still unable to even compile..
dotnet build -f:net7.0-android -c:Release
throws an error
dotnet build -f:net7.0-android -c:Release
MSBuild version 17.4.0+18d5aef85 for .NET
Determining projects to restore...
Restored /builds/roundev/roundev-easy-qr/src/EasyQr/EasyQr.csproj (in 16.86 sec).
/usr/share/dotnet/packs/Microsoft.Android.Sdk.Linux/33.0.4/tools/Xamarin.Android.Tooling.targets(70,5): error XA5300: The Android SDK directory could not be found. Check that the Android SDK Manager in Visual Studio shows a valid installation. To use a custom SDK path for a command line build, set the 'AndroidSdkDirectory' MSBuild property to the custom path. [/builds/roundev/roundev-easy-qr/src/EasyQr/EasyQr.csproj::TargetFramework=net7.0-android]
Build FAILED.
/usr/share/dotnet/packs/Microsoft.Android.Sdk.Linux/33.0.4/tools/Xamarin.Android.Tooling.targets(70,5): error XA5300: The Android SDK directory could not be found. Check that the Android SDK Manager in Visual Studio shows a valid installation. To use a custom SDK path for a command line build, set the 'AndroidSdkDirectory' MSBuild property to the custom path. [/builds/roundev/roundev-easy-qr/src/EasyQr/EasyQr.csproj::TargetFramework=net7.0-android]
0 Warning(s)
1 Error(s)
Time Elapsed 00:00:00.58
Cleaning up project directory and file based variables 00:01
ERROR: Job failed: exit code 1
How should I install and configure this Android SDK?
PS: The fact that the errors show details about Visual Studio (which is a graphical user interface IDE) is a bit concerning.
I've attempted to build in Ubuntu 22.04 with CLI and I am having problems. The goal is to have all this automated in a Docker image.
First I install maui-android
dotnet workload install maui-android --ignore-failed-sources
Then I install Android SDK. 2 Options:
sudo apt update && sudo apt install -y android-sdk
or
sudo snap install androidsdk
Either way, the following attempt to build, fails
dotnet build -f net7.0-android /p:AndroidSdkDirectory=/usr/lib/android-sdk
with error about API level 33 not installed
MSBuild version 17.4.0+18d5aef85 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
/usr/share/dotnet/packs/Microsoft.Android.Sdk.Linux/33.0.4/tools/Xamarin.Android.Tooling.targets(100,5): error XA5207: Could not find android.jar for API level 33. This means the Android SDK platform for API level 33 is not installed. Either install it in the Android SDK Manager (Tools > Open Android SDK Manager...), or change the Xamarin.Android project to target an API version that is installed. (/usr/lib/android-sdk/platforms/android-33/android.jar missing.) [/media/diegosasw/data/src/roundev/roundev-easy-qr/src/EasyQr/EasyQr.csproj::TargetFramework=net7.0-android]
I have attempted to explicitly install API 33
yes | androidsdk "platform-tools" "platforms;android-33"
which creates a ~/AndroidSDK/platforms/android-33
with a jar inside
But
dotnet build -f net7.0-android /p:AndroidSdkDirectory=/home/diegosasw/AndroidSDK/platforms/android-33
also fails
MSBuild version 17.4.0+18d5aef85 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
/usr/share/dotnet/packs/Microsoft.Android.Sdk.Linux/33.0.4/tools/Xamarin.Android.Tooling.targets(70,5): error XA5300: The Android SDK directory could not be found. Check that the Android SDK Manager in Visual Studio shows a valid installation. To use a custom SDK path for a command line build, set the 'AndroidSdkDirectory' MSBuild property to the custom path. [/media/diegosasw/data/src/roundev/roundev-easy-qr/src/EasyQr/EasyQr.csproj::TargetFramework=net7.0-android]
I would appreciate a working sample that I could use both locally in Linux Ubuntu, and in GitLab CI/CD
I got something working locally, but still unable to find a good solution for GitLab CI/CD (too large base image is causing problems)
I have the following *.csproj
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFrameworks>net7.0-android</TargetFrameworks>
<OutputType>Exe</OutputType>
<RootNamespace>MySample</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<EnableDefaultCssItems>false</EnableDefaultCssItems>
<!-- Display name -->
<ApplicationTitle>MySample</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.roundev.mysample</ApplicationId>
<ApplicationIdGuid>840AB3E3-0460-46FE-BDEA-94EEBB053DE2</ApplicationIdGuid>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">
<AndroidKeyStore>True</AndroidKeyStore>
<AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>
<AndroidSigningKeyAlias>myalias</AndroidSigningKeyAlias>
<AndroidSigningKeyPass></AndroidSigningKeyPass>
<AndroidSigningStorePass></AndroidSigningStorePass>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
</ItemGroup>
</Project>
At localhost, I managed to compile and publish signed APK/AAB with following steps:
Assuming SDK .NET 7 is already installed.
dotnet workload install maui-android --ignore-failed-sources
/home/diegosasw/Android/Sdk
keytool -genkey -v -keystore myapp.keystore -alias myalias -keyalg RSA -keysize 2048 -validity 36500
and entered password and details. The generated myapp.keystore, I place it at the same level as the *.csproj
(Ideally it should not be versioned)dotnet build -f net7.0-android /p:AndroidSdkDirectory=/home/diegosasw/Android/Sdk
dotnet publish -f:net7.0-android -c:Release -o dist /p:AndroidSigningKeyPass=<the_password> /p:AndroidSigningStorePass=<the_password> /p:AndroidSdkDirectory=/home/diegosasw/Android/Sdk
and the dist
folder will contain the AAB, signed AAB and signed APK.The process for CI/CD automation would have to be similar, but with automated Android SDK installation, plus other tools,
Firstly I create an image with the following Dockerfile
. It's very heavy, and possibly it could be parametized, and greatly improved.
ARG REPO=mcr.microsoft.com/dotnet/aspnet
FROM $REPO:7.0.1-jammy-amd64 AS platform
ENV \
# Unset ASPNETCORE_URLS from aspnet base image
ASPNETCORE_URLS= \
# Do not generate certificate
DOTNET_GENERATE_ASPNET_CERTIFICATE=false \
# Do not show first run text
DOTNET_NOLOGO=true \
# SDK version
DOTNET_SDK_VERSION=7.0.101 \
# Enable correct mode for dotnet watch (only mode supported in a container)
DOTNET_USE_POLLING_FILE_WATCHER=true \
# Skip extraction of XML docs - generally not useful within an image/container - helps performance
NUGET_XMLDOC_MODE=skip \
# PowerShell telemetry for docker image usage
POWERSHELL_DISTRIBUTION_CHANNEL=PSDocker-DotnetSDK-Ubuntu-22.04
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
git \
wget \
&& rm -rf /var/lib/apt/lists/*
# Install .NET SDK
RUN curl -fSL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz \
&& dotnet_sha512='cf289ad0e661c38dcda7f415b3078a224e8347528448429d62c0f354ee951f4e7bef9cceaf3db02fb52b5dd7be987b7a4327ca33fb9239b667dc1c41c678095c' \
&& echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \
&& mkdir -p /usr/share/dotnet \
&& tar -oxzf dotnet.tar.gz -C /usr/share/dotnet ./packs ./sdk ./sdk-manifests ./templates ./LICENSE.txt ./ThirdPartyNotices.txt \
&& rm dotnet.tar.gz \
# Trigger first run experience by running arbitrary cmd
&& dotnet help
# Install PowerShell global tool
RUN powershell_version=7.3.0 \
&& curl -fSL --output PowerShell.Linux.x64.$powershell_version.nupkg https://pwshtool.blob.core.windows.net/tool/$powershell_version/PowerShell.Linux.x64.$powershell_version.nupkg \
&& powershell_sha512='c4a72142e2bfae0c2a64a662f1baa27940f1db8a09384c90843163e339581d8d41824145fb9f79c680f9b7906043365e870d48d751ab8809c15a590f47562ae6' \
&& echo "$powershell_sha512 PowerShell.Linux.x64.$powershell_version.nupkg" | sha512sum -c - \
&& mkdir -p /usr/share/powershell \
&& dotnet tool install --add-source / --tool-path /usr/share/powershell --version $powershell_version PowerShell.Linux.x64 \
&& dotnet nuget locals all --clear \
&& rm PowerShell.Linux.x64.$powershell_version.nupkg \
&& ln -s /usr/share/powershell/pwsh /usr/bin/pwsh \
&& chmod 755 /usr/share/powershell/pwsh \
# To reduce image size, remove the copy nupkg that nuget keeps.
&& find /usr/share/powershell -print | grep -i '.*[.]nupkg$' | xargs rm
# JAVA
RUN apt-get update && \
apt-get install -y openjdk-11-jdk && \
rm -rf /var/lib/apt/lists/*
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
# Install workload maui
RUN dotnet workload install maui-android --ignore-failed-sources
# Utils
RUN apt-get update && apt-get install -y \
unzip \
jq \
bzip2 \
libzip4 \
libzip-dev && \
rm -rf /var/lib/apt/lists/*
# Install Android SDK
RUN mkdir -p /usr/lib/android-sdk/cmdline-tools/latest && \
curl -k "https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip" -o commandlinetools-linux.zip && \
unzip -q commandlinetools-linux.zip -d /usr/lib/android-sdk/tmp && \
mv /usr/lib/android-sdk/tmp/cmdline-tools/* /usr/lib/android-sdk/cmdline-tools/latest && \
rm -rf /usr/lib/android-sdk/tmp/ && \
rm commandlinetools-linux.zip
ENV ANDROID_SDK_ROOT=/usr/lib/android-sdk
ENV PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH
RUN yes | sdkmanager --licenses && \
sdkmanager "platform-tools" && \
sdkmanager "ndk-bundle" && \
sdkmanager "build-tools;33.0.0" "platforms;android-33"
At the repo in GitLab, I create a secure file and upload the myapp.keystore
(and I remove it from the filesystem).
At the repo in GitLab, I create a CI/CD environment variable KEYSTORE_PASSWORD
with the password value
The idea is to have a .gitlab-ci.yml
similar to this
image: registry.gitlab.com/your_image_generated_out_of_Dockerfile:latest
variables:
PUBLISH_OUTPUT_DIR: dist
ANDROID_SIGNING_KEY_PASS: $KEYSTORE_PASSWORD
ANDROID_SIGNING_STORE_PASS: $KEYSTORE_PASSWORD
stages:
- build
- publish
- delivery
build:
stage: build
script:
- dotnet build -f net7.0-android /p:AndroidSdkDirectory=/usr/lib/android-sdk
artifacts:
paths:
- test
expire_in: 8 hour
rules:
- if: '$CI_COMMIT_TAG == null'
publish_android:
stage: publish
variables:
SECURE_FILES_DOWNLOAD_PATH: './'
before_script:
- APP_VERSION=$(cat ./version/semver)
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
script:
- dotnet publish
-f:net7.0-android
-c:Release
-o $PUBLISH_OUTPUT_DIR
/p:AndroidSigningKeyPass=$ANDROID_SIGNING_KEY_PASS
/p:AndroidSigningStorePass=$ANDROID_SIGNING_STORE_PASS
/p:AndroidSdkDirectory=/usr/lib/android-sdk
-p:Version=$APP_VERSION
allow_failure: false
artifacts:
paths:
- $PUBLISH_OUTPUT_DIR/
expire_in: 8 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
- if: '$CI_COMMIT_TAG == null'
but unfortunately I cannot even generate the Docker image to use as base image because I run out of space due to the huge image size I'm attempting to build.
The question remains:
dotnet build
and dotnet publish
of .NET 7 Maui projects (image with .NET 7 SDK, Android SDK, maui-android workload, JDK and other required tooling)?Upvotes: 10
Views: 3858
Reputation: 15684
I got it working and with a smaller custom image built on the top of the "official" Microsoft's SDK .NET 7, without adding Android NDK (which I don't think it was needed).
This is my docker image
FROM mcr.microsoft.com/dotnet/sdk:7.0
MAINTAINER roundev
# JAVA
RUN apt-get update && \
apt-get install -y openjdk-11-jdk && \
rm -rf /var/lib/apt/lists/*
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
# Install workload maui
RUN dotnet workload install maui-android --ignore-failed-sources
# Utils
RUN apt-get update && apt-get install -y \
unzip && \
rm -rf /var/lib/apt/lists/*
# Install Android SDK
RUN mkdir -p /usr/lib/android-sdk/cmdline-tools/latest && \
curl -k "https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip" -o commandlinetools-linux.zip && \
unzip -q commandlinetools-linux.zip -d /usr/lib/android-sdk/tmp && \
mv /usr/lib/android-sdk/tmp/cmdline-tools/* /usr/lib/android-sdk/cmdline-tools/latest && \
rm -rf /usr/lib/android-sdk/tmp/ && \
rm commandlinetools-linux.zip
ENV ANDROID_SDK_ROOT=/usr/lib/android-sdk
ENV PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH
RUN yes | sdkmanager --licenses && \
sdkmanager "platform-tools" && \
#sdkmanager "ndk-bundle" && \ # I Don't think this was needed at all
sdkmanager "build-tools;33.0.0" "platforms;android-33"
This, generated and stored in GitLab container registry, takes "only" 2.5GB.
So now the pipeline is successful with this .gitlab-ci.yml
image: registry.gitlab.com/roundev/devops/dotnet-maui:latest
variables:
PUBLISH_OUTPUT_DIR: dist
ANDROID_SIGNING_KEY_PASS: $KEYSTORE_PASSWORD
ANDROID_SIGNING_STORE_PASS: $KEYSTORE_PASSWORD
stages:
- build
- publish
- delivery
build:
stage: build
script:
- dotnet build -f net7.0-android /p:AndroidSdkDirectory=/usr/lib/android-sdk
rules:
- if: '$CI_COMMIT_TAG == null'
publish_android:
stage: publish
variables:
SECURE_FILES_DOWNLOAD_PATH: './'
before_script:
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
script:
- dotnet publish
-f:net7.0-android
-c:Release
-o $PUBLISH_OUTPUT_DIR
/p:AndroidSigningKeyPass=$ANDROID_SIGNING_KEY_PASS
/p:AndroidSigningStorePass=$ANDROID_SIGNING_STORE_PASS
/p:AndroidSdkDirectory=/usr/lib/android-sdk
allow_failure: false
artifacts:
paths:
- $PUBLISH_OUTPUT_DIR/
expire_in: 8 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
- if: '$CI_COMMIT_TAG == null'
Notice it is using a secure file where I upload my myapp.keystore
and a CI/CD variable KEYSTORE_PASSWORD
with the signing key pass and store pass (it's the same in my case).
Upvotes: 11