Skip to content

Commit

Permalink
Adding HelloiOS sample for NativeAOT (#82249)
Browse files Browse the repository at this point in the history
- The application is intended to be used by developers who work on enabling NativeAOT on iOS-like platforms and can serve as PoC
- Currently still relies on dotnet/runtime internals and requires local builds instead of package references
  • Loading branch information
ivanpovazan authored Feb 24, 2023
1 parent 9281022 commit 78f8010
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 16 deletions.
50 changes: 50 additions & 0 deletions src/mono/sample/iOS-NativeAOT/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.DEFAULT_GOAL := all

TOP=../../../../

BUILD_CONFIG?=Debug
TARGET_ARCH?=$(shell . $(TOP)eng/native/init-os-and-arch.sh && echo $${arch})
TARGET_OS?=iossimulator
DEPLOY_AND_RUN?=false
STRIP_DEBUG_SYMBOLS?=false

REPO_DIR=$(realpath $(TOP))
TASKS_DIR=$(REPO_DIR)/src/tasks
DOTNET=$(REPO_DIR)/dotnet.sh
BUILD_SCRIPT=$(REPO_DIR)/build.sh

world: build-deps all

# build all dependencies: runtime, nativeAot and all the libs
build-deps: build-runtime-ilc build-libs-all

# building for host
build-runtime-ilc:
$(BUILD_SCRIPT) clr+clr.aot -c $(BUILD_CONFIG)

build-ilc:
$(BUILD_SCRIPT) clr.aot -c $(BUILD_CONFIG)

# building for target platform
build-libs-all:
$(BUILD_SCRIPT) clr.nativeaotruntime+clr.nativeaotlibs+libs -c $(BUILD_CONFIG) -os $(TARGET_OS) -arch $(TARGET_ARCH)

build-libs-nativeaot:
$(BUILD_SCRIPT) clr.nativeaotruntime+clr.nativeaotlibs -c $(BUILD_CONFIG) -os $(TARGET_OS) -arch $(TARGET_ARCH)

all: appbuilder hello-app

appbuilder:
$(DOTNET) build -c $(BUILD_CONFIG) $(TASKS_DIR)/AppleAppBuilder/AppleAppBuilder.csproj

hello-app: clean
$(DOTNET) \
build -c $(BUILD_CONFIG) \
-p:TargetOS=$(TARGET_OS) \
-p:TargetArchitecture=$(TARGET_ARCH) \
-p:DeployAndRun=$(DEPLOY_AND_RUN) \
-p:StripDebugSymbols=$(STRIP_DEBUG_SYMBOLS) \
-bl

clean:
rm -rf obj bin
95 changes: 95 additions & 0 deletions src/mono/sample/iOS-NativeAOT/Program.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutputPath>bin</OutputPath>
<IntermediateOutputPath>$(MSBuildThisFileDirectory)/obj/</IntermediateOutputPath>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<TargetOS Condition="'$(TargetOS)' == ''">ios</TargetOS>
<TargetOS Condition="'$(TargetsiOSSimulator)' == 'true'">iossimulator</TargetOS>
<DeployAndRun Condition="'$(DeployAndRun)' == ''">true</DeployAndRun>
<RuntimeIdentifier>$(TargetOS)-$(TargetArchitecture)</RuntimeIdentifier>
<AppName>HelloiOS</AppName>
<StripDebugSymbols Condition="'$(StripDebugSymbols)' == ''">false</StripDebugSymbols>
<!-- NativeAOT-specific props -->
<NativeLib>static</NativeLib>
<CustomNativeMain>true</CustomNativeMain>
<UseNativeAOTRuntime Condition="'$(UseNativeAOTRuntime)' == ''">true</UseNativeAOTRuntime>
<!-- FIXME: Once we support building System.Globalization.Native and icu, should be removed -->
<InvariantGlobalization>true</InvariantGlobalization>
<!-- FIXME: We do not use publish targets yet, but we need to create a publish directory -->
<PublishDir Condition="'$(PublishDir)' == ''">$(OutputPath)/publish</PublishDir>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\iOS\Program.cs" Link="Program.cs" />
<DirectPInvoke Include="__Internal" />
</ItemGroup>

<PropertyGroup Condition="'$(TargetOS)' == 'maccatalyst'">
<DevTeamProvisioning Condition="'$(DevTeamProvisioning)' == ''">adhoc</DevTeamProvisioning>
<EnableAppSandbox Condition="'$(EnableAppSandbox)' == ''">false</EnableAppSandbox>
</PropertyGroup>

<Import Project="$(CoreClrProjectRoot)nativeaot\BuildIntegration\Microsoft.NETCore.Native.targets" />
<UsingTask TaskName="AppleAppBuilderTask"
AssemblyFile="$(AppleAppBuilderTasksAssemblyPath)" />

<!-- FIXME: Once we set up builing appropriate runtime package for iOS-like platforms the following properties should be removed -->
<Target Name="ConfigureIlcPathsForiOSCrossCompilation">
<PropertyGroup>
<IlcPath>$([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'coreclr', '$(HostOS).$(BuildArchitecture).$(CoreCLRConfiguration)', 'ilc'))</IlcPath>
<IlcToolsPath>$(IlcPath)</IlcToolsPath>
<IlcSdkPath>$(CoreCLRAotSdkDir)</IlcSdkPath>
<IlcFrameworkPath>$(LibrariesAllBinArtifactsPath)</IlcFrameworkPath>
<IlcFrameworkNativePath>$(LibrariesAllBinArtifactsPath)</IlcFrameworkNativePath>
</PropertyGroup>
</Target>

<Target Name="BuildAppBundle"
AfterTargets="Build"
DependsOnTargets="ConfigureIlcPathsForiOSCrossCompilation;SetupProperties;ComputeIlcCompileInputs;IlcCompile">

<PropertyGroup>
<AppDir>$(MSBuildThisFileDirectory)$(PublishDir)\app</AppDir>
<IosSimulator Condition="'$(TargetsiOSSimulator)' == 'true'">iPhone 11</IosSimulator>
<Optimized Condition="'$(Configuration)' == 'Release'">True</Optimized>
</PropertyGroup>

<ItemGroup>
<NativeLibrary Include="%(ManagedBinary.IlcOutputFile)" />
</ItemGroup>

<RemoveDir Directories="$(AppDir)" />

<AppleAppBuilderTask
UseNativeAOTRuntime="$(UseNativeAOTRuntime)"
NativeDependencies="@(NativeLibrary)"
TargetOS="$(TargetOS)"
Arch="$(TargetArchitecture)"
ProjectName="$(AppName)"
Assemblies="@(BundleAssemblies)"
GenerateXcodeProject="True"
BuildAppBundle="True"
DevTeamProvisioning="$(DevTeamProvisioning)"
OutputDirectory="$(AppDir)"
Optimized="$(Optimized)"
InvariantGlobalization="$(InvariantGlobalization)"
StripSymbolTable="$(StripDebugSymbols)"
AppDir="$(MSBuildThisFileDirectory)$(PublishDir)" >
<Output TaskParameter="AppBundlePath" PropertyName="AppBundlePath" />
<Output TaskParameter="XcodeProjectPath" PropertyName="XcodeProjectPath" />
</AppleAppBuilderTask>

<Message Importance="High" Text="Xcode: $(XcodeProjectPath)"/>
<Message Importance="High" Text="App: $(AppBundlePath)"/>

</Target>

<Target Name="RunAppBundle"
AfterTargets="BuildAppBundle"
Condition="'$(DeployAndRun)' == 'true'">
<Exec Condition="'$(TargetOS)' == 'iossimulator'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=ios-simulator-64 --output-directory=/tmp/out" />
<Exec Condition="'$(TargetOS)' == 'ios'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=ios-device --output-directory=/tmp/out" />
</Target>

</Project>
79 changes: 79 additions & 0 deletions src/mono/sample/iOS-NativeAOT/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# NativeAOT iOS sample app

## Description

This sample application is intended to be used by developers who work on enabling NativeAOT on iOS-like platforms and can serve as PoC for verifying support for the following systems:
- ios
- iossimulator
- tvos
- tvossimulator
- maccatalyst

The sample shares the source code with the Mono sample specified at: `../iOS/Program.cs` and in general should have the same behavior as MonoAOT.

## Limitations

The application is **_currently_** relying on the following:
1. Internal dependencies - locally building the internals is required as runtime and tools nuget packages are still not being produced
2. Invariant globalization - `System.Globalization.Native` is currently not being built as part of NativeAOT framework for iOS-like platforms
3. No publish targets - the SDK and MSBuild integration is still not complete

## How to build and test

### Building for the first time

When building for the first time (on a clean checkout) run from this directory the following `make` command:
``` bash
make world
```
This will first build all required runtime components and dependencies, after which it will build the sample app and bundle it into an application bundle.
By default the build will use `Debug` build configuration and target `iossimulator`.
To change this behavior, specify the desired setting in the following way:
``` bash
make world BUILD_CONFIG=Release TARGET_OS=ios
```

### To avoid building all the dependencies

For future builds, you can run just:
``` bash
make
```
which will skip building all the runtime dependencies, assuming those have been already properly built, and build the MSBuild task used for bundling the application and the application it self.

For convenience, it is also possible to rebuild only the application it self with:
``` bash
make hello-app
```

### Deploy and run

#### Simulator

To test the application on a simulator include the following in your make command `DEPLOY_AND_RUN=true` e.g.,:
``` bash
make hello-app DEPLOY_AND_RUN=true
```

#### Device

To test the application on a device, a provisioning profile needs to be specified.
This can be achieved by defining `DevTeamProvisioning` environment variable with a valid team ID (see [developer.apple.com/account/#/membership](https://developer.apple.com/account/#/membership), scroll down to `Team ID`) for example:
``` bash
export DevTeamProvisioning=A1B2C3D4E5; make hello-app TARGET_OS=ios DEPLOY_AND_RUN=true
```
Assuming `A1B2C3D4E5` is a valid team ID.

#### One-liner

On a clean dotnet/runtime checkout, from this directory, run:

``` bash
export DevTeamProvisioning=A1B2C3D4E5; make world BUILD_CONFIG=Release TARGET_OS=ios DEPLOY_AND_RUN=true
```

This command will build everything necessary to run and deploy the application on an iOS device.

### Custom builds

Check the `Makefile` for individual list of targets and variables to customize the build.
2 changes: 1 addition & 1 deletion src/mono/sample/iOS/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static async Task Main(string[] args)
delegate* unmanaged<void> unmanagedPtr = &OnButtonClick;
ios_register_button_click(unmanagedPtr);
}
const string msg = "Hello World!\n.NET 5.0";
const string msg = "Hello World!\n.NET 8.0";
for (int i = 0; i < msg.Length; i++)
{
// a kind of an animation
Expand Down
10 changes: 10 additions & 0 deletions src/tasks/AppleAppBuilder/AppleAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ public string TargetOS
/// </summary>
public bool UseNativeAOTRuntime { get; set; }

/// <summary>
/// Extra native dependencies to link into the app
/// </summary>
public string[] NativeDependencies { get; set; } = Array.Empty<string>();

public void ValidateRuntimeSelection()
{
if (UseNativeAOTRuntime)
Expand Down Expand Up @@ -267,6 +272,11 @@ public override bool Execute()
}
}

foreach (var nativeDependency in NativeDependencies)
{
assemblerFilesToLink.Add(nativeDependency);
}

if (!ForceInterpreter && (isDevice || ForceAOT) && (assemblerFiles.Count == 0 && !UseNativeAOTRuntime))
{
throw new InvalidOperationException("Need list of AOT files for device builds.");
Expand Down
11 changes: 10 additions & 1 deletion src/tasks/AppleAppBuilder/Templates/CMakeLists.txt.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ add_executable(
%ProjectName%
%MainSource%
${APP_RESOURCES}
util.h
util.m
)

if(NOT %UseNativeAOTRuntime%)
Expand Down Expand Up @@ -73,5 +75,12 @@ target_link_libraries(
"-lz"
"-lc++"
"-liconv"
%NativeLibrariesToLink%
%NativeLibrariesToLink%
)

if(%UseNativeAOTRuntime%)
target_link_libraries(
%ProjectName%
"-Wl,-u,_NativeAOT_StaticInitialization"
)
endif()
11 changes: 8 additions & 3 deletions src/tasks/AppleAppBuilder/Templates/main-simple.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#if !USE_NATIVE_AOT
#import "runtime.h"
#else
extern void* NativeAOT_StaticInitialization();
#import <os/log.h>
#import "util.h"
extern int __managed__Main(int argc, char* argv[]);
#endif

Expand Down Expand Up @@ -57,8 +58,12 @@ - (void)viewDidLoad {
#if INVARIANT_GLOBALIZATION
setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", TRUE);
#endif
NativeAOT_StaticInitialization();
int ret_val = __managed__Main(0, NULL);
char **managed_argv;
int managed_argc = get_managed_args (&managed_argv);
int ret_val = __managed__Main (managed_argc, managed_argv);
free_managed_args (&managed_argv, managed_argc);
os_log_info (OS_LOG_DEFAULT, EXIT_CODE_TAG ": %d", ret_val);
exit (ret_val);
#endif
});
}
Expand Down
17 changes: 6 additions & 11 deletions src/tasks/AppleAppBuilder/Templates/runtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
#include <stdlib.h>
#include <stdio.h>

#import "util.h"

static char *bundle_path;

#define APPLE_RUNTIME_IDENTIFIER "//%APPLE_RUNTIME_IDENTIFIER%"

#define RUNTIMECONFIG_BIN_FILE "runtimeconfig.bin"

// XHarness is looking for this tag in app's output to determine the exit code
#define EXIT_CODE_TAG "DOTNET.APP_EXIT_CODE"

const char *
get_bundle_path (void)
{
Expand Down Expand Up @@ -254,14 +253,8 @@
setenv ("DOTNET_DiagnosticPorts", DIAGNOSTIC_PORTS, true);
#endif

id args_array = [[NSProcessInfo processInfo] arguments];
assert ([args_array count] <= 128);
const char *managed_argv [128];
int argi;
for (argi = 0; argi < [args_array count]; argi++) {
NSString* arg = [args_array objectAtIndex: argi];
managed_argv[argi] = [arg UTF8String];
}
char **managed_argv;
int argi = get_managed_args (&managed_argv);

bool wait_for_debugger = FALSE;

Expand Down Expand Up @@ -373,5 +366,7 @@

mono_jit_cleanup (domain);

free_managed_args (&managed_argv, argi);

exit (res);
}
13 changes: 13 additions & 0 deletions src/tasks/AppleAppBuilder/Templates/util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#ifndef util_h
#define util_h

// XHarness is looking for this tag in app's output to determine the exit code
#define EXIT_CODE_TAG "DOTNET.APP_EXIT_CODE"

size_t get_managed_args (char*** managed_args_array);
void free_managed_args (char*** managed_args_array, size_t array_size);

#endif /* util_h */
Loading

0 comments on commit 78f8010

Please sign in to comment.