Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mbr] Add Apple sample #50740

Merged
merged 4 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions src/mono/sample/mbr/DeltaHelper/DeltaHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,9 @@

namespace MonoDelta {
public class DeltaHelper {
private static Action<Assembly, byte[], byte[], byte[]> _updateMethod;

private static Action<Assembly, byte[], byte[], byte[]> UpdateMethod => _updateMethod ?? InitUpdateMethod();

private static Action<Assembly, byte[], byte[], byte[]> InitUpdateMethod ()
{
var monoType = typeof(System.Reflection.Metadata.AssemblyExtensions);
const string methodName = "ApplyUpdate";
var mi = monoType.GetMethod (methodName, BindingFlags.Public | BindingFlags.Static);
if (mi == null)
throw new Exception ($"Couldn't get {methodName} from {monoType.FullName}");
_updateMethod = MakeUpdateMethod (mi); //Delegate.CreateDelegate (typeof(Action<Assembly, byte[], byte[], byte[]>), mi) as Action<Assembly, byte[], byte[], byte[]>;
return _updateMethod;
}

private static Action<Assembly, byte[], byte[], byte[]> MakeUpdateMethod (MethodInfo applyUpdate)
{
// Make
// void ApplyUpdateArray (Assembly a, byte[] dmeta, byte[] dil, byte[] dpdb)
// {
// ApplyUpdate (a, (ReadOnlySpan<byte>)dmeta, (ReadOnlySpan<byte>)dil, (ReadOnlySpan<byte>)dpdb);
// }
var dm = new DynamicMethod ("CallApplyUpdate", typeof(void), new Type[] { typeof(Assembly), typeof(byte[]), typeof(byte[]), typeof(byte[])}, typeof (DeltaHelper).Module);
var ilg = dm.GetILGenerator ();
var conv = typeof(ReadOnlySpan<byte>).GetMethod("op_Implicit", new Type[] {typeof(byte[])});

ilg.Emit (OpCodes.Ldarg_0);
ilg.Emit (OpCodes.Ldarg_1);
ilg.Emit (OpCodes.Call, conv);
ilg.Emit (OpCodes.Ldarg_2);
ilg.Emit (OpCodes.Call, conv);
ilg.Emit (OpCodes.Ldarg_3);
ilg.Emit (OpCodes.Call, conv);
ilg.Emit (OpCodes.Call, applyUpdate);
ilg.Emit (OpCodes.Ret);

return dm.CreateDelegate(typeof(Action<Assembly, byte[], byte[], byte[]>)) as Action<Assembly, byte[], byte[], byte[]>;
}

private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data, byte[] dpdb_data)
{
UpdateMethod (assm, dmeta_data, dil_data, dpdb_data);
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate (assm, dmeta_data, dil_data, dpdb_data);
}

DeltaHelper () { }
Expand Down
27 changes: 26 additions & 1 deletion src/mono/sample/mbr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,19 @@ For WebAssembly:

Make sure `EMSDK_PATH` is set (see [workflow](../../../../docs/workflow/building/libraries/webassembly-instructions.md))
```console
build.sh --os browser /p:MonoMetadataUpdate=true
build.sh --os browser
```

For Apple targets:

```console
build.sh --os MacCatalyst -s Mono+Libs
```

or

```console
build.sh --os iOSSimulator -s Mono+Libs
```

## Running
Expand All @@ -50,3 +62,16 @@ make CONFIG=Debug && make CONFIG=Debug run
```

Then go to http://localhost:8000/ and click the button once or twice (the example has 2 updates prebuilt)

For Apple targets:

for ios simulator
```
make run-sim
```

for Mac Catalyst

```
make run-catalyst
```
95 changes: 95 additions & 0 deletions src/mono/sample/mbr/apple/AppleDelta.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>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(TargetOS.ToLower())-$(TargetArchitecture)\$(Configuration)\runtimes\$(TargetOS.ToLower())-$(TargetArchitecture)\</MicrosoftNetCoreAppRuntimePackDir>
<EnableTargetingPackDownload>false</EnableTargetingPackDownload>
<RuntimeIdentifier>$(TargetOS.ToLower())-$(TargetArchitecture)</RuntimeIdentifier>
<DefineConstants Condition="'$(ArchiveTests)' == 'true'">$(DefineConstants);CI_TEST</DefineConstants>
<MonoForceInterpreter>true</MonoForceInterpreter>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="ChangeablePart.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DeltaHelper\DeltaHelper.csproj" />
</ItemGroup>

<PropertyGroup>
<DeltaScript>deltascript.json</DeltaScript>
<DeltaCount>1</DeltaCount>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetOS)' == 'MacCatalyst'">
<DevTeamProvisioning Condition="'$(TargetOS)' == 'MacCatalyst' and '$(DevTeamProvisioning)' == ''">-</DevTeamProvisioning>
</PropertyGroup>

<!-- Redirect 'dotnet publish' to in-tree runtime pack -->
<Target Name="TrickRuntimePackLocation" AfterTargets="ProcessFrameworkReferences">
<ItemGroup>
<RuntimePack>
<PackageDirectory>$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(RuntimeIdentifier)\$(Configuration)</PackageDirectory>
</RuntimePack>
</ItemGroup>
<Message Text="Packaged ID: %(RuntimePack.PackageDirectory)" Importance="high" />
</Target>

<Import Project="$(RepoTasksDir)AotCompilerTask\MonoAOTCompiler.props" />
<UsingTask TaskName="AppleAppBuilderTask"
AssemblyFile="$(AppleAppBuilderTasksAssemblyPath)" />

<Target Name="BuildAppBundle" AfterTargets="CopyFilesToPublishDirectory">
<PropertyGroup>
<AppDir>$(MSBuildThisFileDirectory)$(PublishDir)\app</AppDir>
<IosSimulator Condition="'$(TargetsiOSSimulator)' == 'true'">iPhone 11</IosSimulator>
<Optimized Condition="'$(Configuration)' == 'Release'">True</Optimized>
<RunAOTCompilation Condition="('$(TargetsMacCatalyst)' == 'false' and '$(IosSimulator)' == '') or '$(ForceAOT)' == 'true'">true</RunAOTCompilation>
</PropertyGroup>

<Error Condition="'$(TargetOS)' == ''" Text="The TargetOS property must be set outside the project file" />

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

<ItemGroup>
<BundleAssemblies Condition="'$(RunAOTCompilation)' != 'true'" Include="$(MSBuildThisFileDirectory)$(PublishDir)\*.dll" />
</ItemGroup>

<AppleAppBuilderTask
TargetOS="$(TargetOS)"
Arch="$(TargetArchitecture)"
ProjectName="AppleDelta"
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackDir)native\include\mono-2.0"
Assemblies="@(BundleAssemblies)"
NativeMainSource="$(MSBuildThisFileDirectory)\main.m"
MainLibraryFileName="AppleDelta.dll"
GenerateXcodeProject="True"
BuildAppBundle="True"
DevTeamProvisioning="$(DevTeamProvisioning)"
OutputDirectory="$(AppDir)"
Optimized="$(Optimized)"
ForceAOT="$(ForceAOT)"
ForceInterpreter="$(MonoForceInterpreter)"
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)"/>

<Exec Condition="'$(TargetOS)' == 'iOSSimulator'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=ios-simulator-64 --output-directory=/tmp/out" />

<!-- run on MacCatalyst -->
<Exec Condition="'$(TargetOS)' == 'MacCatalyst'" Command="dotnet xharness apple run --app=$(AppBundlePath) --targets=maccatalyst --output-directory=/tmp/out" />

</Target>

<!-- Set RoslynILDiffFullPath property to the path of roslynildiff -->
<Import Project="..\DeltaHelper.targets" />

</Project>
9 changes: 9 additions & 0 deletions src/mono/sample/mbr/apple/ChangeablePart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

public class ChangeablePart
{
public static int UpdateCounter (ref int counter)
{
return ++counter;
}
}
9 changes: 9 additions & 0 deletions src/mono/sample/mbr/apple/ChangeablePart_v1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

public class ChangeablePart
{
public static int UpdateCounter (ref int counter)
{
return --counter;
}
}
14 changes: 14 additions & 0 deletions src/mono/sample/mbr/apple/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CONFIG=Debug
MONO_ARCH=x64
DOTNET := ../../../../../dotnet.sh

run-sim:
$(DOTNET) publish -c $(CONFIG) /p:TargetOS=iOSSimulator /p:TargetArchitecture=$(MONO_ARCH) \
/p:UseLLVM=False /p:ForceAOT=False /p:MonoForceInterpreter=true

run-catalyst:
$(DOTNET) publish -c $(CONFIG) /p:TargetOS=MacCatalyst /p:TargetArchitecture=$(MONO_ARCH) \
/p:UseLLVM=False /p:ForceAOT=False /p:MonoForceInterpreter=true

clean:
rm -rf bin
59 changes: 59 additions & 0 deletions src/mono/sample/mbr/apple/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

public static class Program
{
// Defined in main.m
[DllImport("__Internal")]
private static extern void ios_set_text(string value);

[DllImport("__Internal")]
unsafe private static extern void ios_register_button_click(delegate* unmanaged<void> callback);

[DllImport("__Internal")]
unsafe private static extern void ios_register_applyupdate_click(delegate* unmanaged<void> callback);

private static int counter = 0;

// Called by native code, see main.m
[UnmanagedCallersOnly]
private static void OnButtonClick()
{
ios_set_text("OnButtonClick! #" + ChangeablePart.UpdateCounter (ref counter));
}

[UnmanagedCallersOnly]
private static void OnApplyUpdateClick()
{
deltaHelper.Update (typeof(ChangeablePart).Assembly);
}

static MonoDelta.DeltaHelper deltaHelper;

public static async Task Main(string[] args)
{
unsafe {
// Register a managed callback (will be called by UIButton, see main.m)
delegate* unmanaged<void> unmanagedPtr = &OnButtonClick;
ios_register_button_click(unmanagedPtr);
delegate* unmanaged<void> unmanagedPtr2 = &OnApplyUpdateClick;
ios_register_applyupdate_click(unmanagedPtr2);
}
deltaHelper = MonoDelta.DeltaHelper.Make();
const string msg = "Hello World!\n.NET 5.0";
for (int i = 0; i < msg.Length; i++)
{
// a kind of an animation
ios_set_text(msg.Substring(0, i + 1));
await Task.Delay(100);
}

Console.WriteLine("Done!");
await Task.Delay(-1);
}
}
5 changes: 5 additions & 0 deletions src/mono/sample/mbr/apple/deltascript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"changes": [
{"document": "ChangeablePart.cs", "update": "ChangeablePart_v1.cs"},
]
}
106 changes: 106 additions & 0 deletions src/mono/sample/mbr/apple/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include <stdlib.h>

#import <UIKit/UIKit.h>
#import "runtime.h"

@interface ViewController : UIViewController
@end

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *controller;
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.controller = [[ViewController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = self.controller;
[self.window makeKeyAndVisible];
return YES;
}
@end

UILabel *label;
void (*clickHandlerPtr)(void);
void (*clickHandlerApplyUpdatePtr)(void);

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

label = [[UILabel alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
label.textColor = [UIColor greenColor];
label.font = [UIFont boldSystemFontOfSize: 30];
label.numberOfLines = 2;
label.textAlignment = NSTextAlignmentCenter;
label.text = @"Hello, wire me up!\n(dllimport ios_set_text)";
[self.view addSubview:label];

UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[button setFrame:CGRectMake(50, 250, 200, 50)];
[button setTitle:@"Click me (wire me up)" forState:UIControlStateNormal];
[button setExclusiveTouch:YES];
[self.view addSubview:button];

UIButton *apply_button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[apply_button addTarget:self action:@selector(applyUpdateButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[apply_button setFrame:CGRectMake(50, 300, 200, 50)];
[apply_button setTitle:@"ApplyUpdate" forState:UIControlStateNormal];
[apply_button setExclusiveTouch:YES];
[self.view addSubview:apply_button];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
mono_ios_runtime_init ();
});
}
-(void) buttonClicked:(UIButton*)sender
{
if (clickHandlerPtr)
clickHandlerPtr();
}

-(void) applyUpdateButtonClicked:(UIButton*)sender
{
if (clickHandlerApplyUpdatePtr)
clickHandlerApplyUpdatePtr();
}

@end

// called from C# sample
void
ios_register_button_click (void* ptr)
{
clickHandlerPtr = ptr;
}

// called from C# sample
void
ios_register_applyupdate_click (void* ptr)
{
clickHandlerApplyUpdatePtr = ptr;
}

// called from C# sample
void
ios_set_text (const char* value)
{
NSString* nsstr = [NSString stringWithUTF8String:strdup(value)];
dispatch_async(dispatch_get_main_queue(), ^{
label.text = nsstr;
});
}

int main(int argc, char * argv[]) {
@autoreleasepool {
setenv("DOTNET_MODIFIABLE_ASSEMBLIES", "Debug", 1);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}