Skip to content

Commit

Permalink
[mono] Add iOS app builder project (#34563)
Browse files Browse the repository at this point in the history
* Add iosAppBuilderTasks project

* Escape spaces in ProjectName

* Remove add_definitions(-DDEVICE)

* Fix typoe Buillder -> Builder, also build the task as part of mono runtime

* Rewrite HelloiOS sample (to use the task)

* Fix build

* Clean up

* Add license headers

* Clean up

* dont build app if provisionig is not set

* Add ExcludeFromAppDir input argument, remove stopwatch

* Drop DOTNET_SYSTEM_GLOBALIZATION_INVARIANT

* Address feedback

* Validate input

* fix build

* Disable GenerateXcodeProject by default

* move to mono/msbuild

* Add OutputDirectory property

* Fix sample

* Minor improvements

* fix build issues
  • Loading branch information
EgorBo committed Apr 14, 2020
1 parent ad8e751 commit dda0a57
Show file tree
Hide file tree
Showing 18 changed files with 912 additions and 174 deletions.
30 changes: 30 additions & 0 deletions src/mono/mono.proj
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,36 @@

<Import Project="Directory.Build.targets" />

<Target Name="BuildAppleAppBuilder">
<MSBuild Projects="$(MonoProjectRoot)msbuild\AppleAppBuilder\AppleAppBuilder.csproj"
Properties="Configuration=$(Configuration)"
Targets="Restore;Build" />
</Target>

<UsingTask TaskName="AppleAppBuilderTask"
AssemblyFile="$(ArtifactsObjDir)mono\AppleAppBuilder\$(TargetArchitecture)\$(Configuration)\AppleAppBuilder.dll" />

<Target Name="BuildAppleApp" DependsOnTargets="BuildAppleAppBuilder">
<AppleAppBuilderTask
Arch="$(TargetArchitecture)"
ProjectName="$(ProjectName)"
Optimized="$(Optimized)"
MonoInclude="$(BinDir)include\mono-2.0"
CrossCompiler="$(BinDir)cross\mono-aot-cross"
MainLibraryFileName="$(MainLibraryFileName)"
NativeMainSource="$(NativeMainSource)"
GenerateXcodeProject="$(GenerateXcodeProject)"
BuildAppBundle="$(BuildAppBundle)"
DevTeamProvisioning="$(DevTeamProvisioning)"
OutputDirectory="$(BinDir)\$(ProjectName)"
AppDir="$(AppDir)">
<Output TaskParameter="AppBundlePath" PropertyName="AppBundlePath" />
<Output TaskParameter="XcodeProjectPath" PropertyName="XcodeProjectPath" />
</AppleAppBuilderTask>
<Message Importance="High" Text="Xcode: $(XcodeProjectPath)"/>
<Message Importance="High" Text="App: $(AppBundlePath)"/>
</Target>

<!-- Ordering matters! Overwriting the Build target. -->
<!-- General targets -->
<Target Name="Build" DependsOnTargets="BuildMonoRuntimeUnix;BuildMonoRuntimeWindows">
Expand Down
140 changes: 140 additions & 0 deletions src/mono/msbuild/AppleAppBuilder/AotCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

internal class AotCompiler
{
/// <summary>
/// Precompile all assemblies in parallel
/// </summary>
public static void PrecompileLibraries(
string crossCompiler,
string arch,
bool parallel,
string binDir,
string[] libsToPrecompile,
IDictionary<string, string> envVariables,
bool optimize)
{
Parallel.ForEach(libsToPrecompile,
new ParallelOptions { MaxDegreeOfParallelism = parallel ? Environment.ProcessorCount : 1 },
lib => PrecompileLibrary(crossCompiler, arch, binDir, lib, envVariables, optimize));
}

private static void PrecompileLibrary(
string crossCompiler,
string arch,
string binDir,
string libToPrecompile,
IDictionary<string, string> envVariables,
bool optimize)
{
Utils.LogInfo($"[AOT] {libToPrecompile}");

var crossArgs = new StringBuilder();
crossArgs
.Append(" -O=gsharedvt,float32")
.Append(" --nollvm")
.Append(" --debug");

string libName = Path.GetFileNameWithoutExtension(libToPrecompile);
var aotArgs = new StringBuilder();
aotArgs
.Append("mtriple=").Append(arch).Append("-ios,")
.Append("static,")
.Append("asmonly,")
.Append("direct-icalls,")
.Append("no-direct-calls,")
.Append("dwarfdebug,")
.Append("outfile=").Append(Path.Combine(binDir, libName + ".dll.s,"))
// TODO: enable aotdata
//.Append("data-outfile=").Append(Path.Combine(binDir, libName + ".aotdata,"))
// TODO: enable direct-pinvokes (to get rid of -force_loads)
//.Append("direct-pinvoke,")
.Append("full,");

// TODO: enable Interpreter
// TODO: enable LLVM
// TODO: enable System.Runtime.Intrinsics.Arm (LLVM-only for now)
// e.g. .Append("mattr=+crc,")

crossArgs
.Append(" --aot=").Append(aotArgs).Append(" ")
.Append(libToPrecompile);

Utils.RunProcess(crossCompiler, crossArgs.ToString(), envVariables, binDir);

var clangArgs = new StringBuilder();
if (optimize)
{
clangArgs.Append(" -Os");
}
clangArgs
.Append(" -isysroot ").Append(Xcode.Sysroot)
.Append(" -miphoneos-version-min=10.1")
.Append(" -arch ").Append(arch)
.Append(" -c ").Append(Path.Combine(binDir, libName)).Append(".dll.s")
.Append(" -o ").Append(Path.Combine(binDir, libName)).Append(".dll.o");

Utils.RunProcess("clang", clangArgs.ToString(), workingDir: binDir);
}

public static void GenerateLinkAllFile(string[] objFiles, string outputFile)
{
// Generates 'modules.m' in order to register all managed libraries
//
//
// extern void *mono_aot_module_Lib1_info;
// extern void *mono_aot_module_Lib2_info;
// ...
//
// void mono_ios_register_modules (void)
// {
// mono_aot_register_module (mono_aot_module_Lib1_info);
// mono_aot_register_module (mono_aot_module_Lib2_info);
// ...
// }

Utils.LogInfo("Generating 'modules.m'...");

var lsDecl = new StringBuilder();
lsDecl
.AppendLine("#include <mono/jit/jit.h>")
.AppendLine("#include <TargetConditionals.h>")
.AppendLine()
.AppendLine("#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR")
.AppendLine();

var lsUsage = new StringBuilder();
lsUsage
.AppendLine("void mono_ios_register_modules (void)")
.AppendLine("{");
foreach (string objFile in objFiles)
{
string symbol = "mono_aot_module_" +
Path.GetFileName(objFile)
.Replace(".dll.o", "")
.Replace(".", "_")
.Replace("-", "_") + "_info";

lsDecl.Append("extern void *").Append(symbol).Append(';').AppendLine();
lsUsage.Append("\tmono_aot_register_module (").Append(symbol).Append(");").AppendLine();
}
lsDecl
.AppendLine()
.Append(lsUsage)
.AppendLine("}")
.AppendLine()
.AppendLine("#endif")
.AppendLine();

File.WriteAllText(outputFile, lsDecl.ToString());
Utils.LogInfo($"Saved to {outputFile}.");
}
}
187 changes: 187 additions & 0 deletions src/mono/msbuild/AppleAppBuilder/AppleAppBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class AppleAppBuilderTask : Task
{
/// <summary>
/// Path to arm64 AOT cross-compiler (mono-aot-cross)
/// It's not used for x64 (Simulator)
/// </summary>
public string? CrossCompiler { get; set; }

/// <summary>
/// ProjectName is used as an app name, bundleId and xcode project name
/// </summary>
[Required]
public string ProjectName { get; set; } = ""!;

/// <summary>
/// Target directory with *dll and other content to be AOT'd and/or bundled
/// </summary>
[Required]
public string AppDir { get; set; } = ""!;

/// <summary>
/// Path to Mono public headers (*.h)
/// </summary>
[Required]
public string MonoInclude { get; set; } = ""!;

/// <summary>
/// This library will be used as an entry-point (e.g. TestRunner.dll)
/// </summary>
[Required]
public string MainLibraryFileName { get; set; } = ""!;

/// <summary>
/// Path to store build artifacts
/// </summary>
public string? OutputDirectory { get; set; }

/// <summary>
/// Produce optimized binaries (e.g. use -O2 in AOT)
/// and use 'Release' config in xcode
/// </summary>
public bool Optimized { get; set; }

/// <summary>
/// Disable parallel AOT compilation
/// </summary>
public bool DisableParallelAot { get; set; }

/// <summary>
/// Target arch, can be "arm64" (device) or "x64" (simulator) at the moment
/// </summary>
[Required]
public string Arch { get; set; } = ""!;

/// <summary>
/// DEVELOPER_TEAM provisioning, needed for arm64 builds.
/// </summary>
public string? DevTeamProvisioning { get; set; }

/// <summary>
/// Build *.app bundle (using XCode for now)
/// </summary>
public bool BuildAppBundle { get; set; }

/// <summary>
/// Generate xcode project
/// </summary>
public bool GenerateXcodeProject { get; set; }

/// <summary>
/// Files to be ignored in AppDir
/// </summary>
public ITaskItem[]? ExcludeFromAppDir { get; set; }

/// <summary>
/// Path to a custom main.m with custom UI
/// A default one is used if it's not set
/// </summary>
public string? NativeMainSource { get; set; }

/// <summary>
/// Use Console-style native UI template
/// (use NativeMainSource to override)
/// </summary>
public bool UseConsoleUITemplate { get; set; }

/// <summary>
/// Path to *.app bundle
/// </summary>
[Output]
public string AppBundlePath { get; set; } = ""!;

/// <summary>
/// Path to xcode project
/// </summary>
[Output]
public string XcodeProjectPath { get; set; } = ""!;

public override bool Execute()
{
Utils.Logger = Log;
bool isDevice = Arch.Equals("arm64", StringComparison.InvariantCultureIgnoreCase);
if (isDevice && string.IsNullOrEmpty(CrossCompiler))
{
throw new ArgumentException("arm64 arch requires CrossCompiler");
}

if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
{
throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
}

// escape spaces
ProjectName = ProjectName.Replace(" ", "-");

string[] excludes = new string[0];
if (ExcludeFromAppDir != null)
{
excludes = ExcludeFromAppDir
.Where(i => !string.IsNullOrEmpty(i.ItemSpec))
.Select(i => i.ItemSpec)
.ToArray();
}
string[] libsToAot = Directory.GetFiles(AppDir, "*.dll")
.Where(f => !excludes.Contains(Path.GetFileName(f)))
.ToArray();

string binDir = Path.Combine(AppDir, $"bin-{ProjectName}-{Arch}");
if (!string.IsNullOrEmpty(OutputDirectory))
{
binDir = OutputDirectory;
}
Directory.CreateDirectory(binDir);

// run AOT compilation only for devices
if (isDevice)
{
if (string.IsNullOrEmpty(CrossCompiler))
throw new InvalidOperationException("cross-compiler is not set");

AotCompiler.PrecompileLibraries(CrossCompiler, Arch, !DisableParallelAot, binDir, libsToAot,
new Dictionary<string, string> { {"MONO_PATH", AppDir} },
Optimized);
}

// generate modules.m
AotCompiler.GenerateLinkAllFile(
Directory.GetFiles(binDir, "*.dll.o"),
Path.Combine(binDir, "modules.m"));

if (GenerateXcodeProject)
{
XcodeProjectPath = Xcode.GenerateXCode(ProjectName, MainLibraryFileName,
AppDir, binDir, MonoInclude, UseConsoleUITemplate, NativeMainSource);

if (BuildAppBundle)
{
if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning))
{
// DevTeamProvisioning shouldn't be empty for arm64 builds
Utils.LogInfo("DevTeamProvisioning is not set, BuildAppBundle step is skipped.");
}
else
{
AppBundlePath = Xcode.BuildAppBundle(
Path.Combine(binDir, ProjectName, ProjectName + ".xcodeproj"),
Arch, Optimized, DevTeamProvisioning);
}
}
Utils.LogInfo($"Xcode: {XcodeProjectPath}\n App: {AppBundlePath}");
}

return true;
}
}
25 changes: 25 additions & 0 deletions src/mono/msbuild/AppleAppBuilder/AppleAppBuilder.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<OutputPath>bin</OutputPath>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Templates\*.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="AotCompiler.cs" />
<Compile Include="AppleAppBuilder.cs" />
<Compile Include="Utils.cs" />
<Compile Include="Xcode.cs" />
</ItemGroup>
</Project>
Loading

0 comments on commit dda0a57

Please sign in to comment.