Skip to content

Commit

Permalink
[build] Apple Silicon M1 build support
Browse files Browse the repository at this point in the history
Fixes: dotnet#5518

Context: https://github.com/xamarin/xamarin-android/projects/16

[Apple announced new Mac hardware][0], which use an [M1 CPU][1], which
is an arm64-compatible ABI, *not* intel based.  Legacy software built
for intel CPUs will still run via the [Rosetta][3] translation
environment.

Because of Rosetta, existing Xamarin.Android binaries will work as-is
on Mac hardware with M1 CPUs.

However, what if you want to *build* the xamarin-android repo on an
M1 CPU?

Previously, that would fail:

	% make prepare PREPARE_AUTOPROVISION=true
	…
	  Error: Could not find Homebrew on this system, please install it from https://brew.sh/

Unfortunately, [Homebrew][4] won't install by default:

	% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

	Password:
	Homebrew is not (yet) supported on ARM processors!
	Rerun the Homebrew installer under Rosetta 2.
	If you really know what you are doing and are prepared for a very broken
	experience you can use another installation option for installing on ARM:
	  https://docs.brew.sh/Installation

However, there *is* a way to install Homebrew!  You "just" need to
"lie" about the processor being used!

	% arch -arch x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
	# works!

This works by using the [**arch**(1)][5] command, which allows
explicitly specifying (overriding) the CPU environment that the child
process runs within.  Within `arch -arch x86_64 command`, `command`
believes it's running on an x86_64 CPU.

Update the Unix `dependencies.md` documentation to document how to
install Homebrew on an M1 CPU.

Update `xaprepare` so that whenever a `brew` command is executed on
an M1 CPU, the command is prefixed with `arch -arch x86_64`.

`xaprepare` uses the [`sysctl.proc_translated` system control][6]
to determine if it's currently running within a Rosetta environment,
so that `arch -arch x86_64` is not used unless required.

[0]: https://www.apple.com/newsroom/2020/11/introducing-the-next-generation-of-mac/
[1]: https://www.apple.com/newsroom/2020/11/apple-unleashes-m1/
[2]: https://www.apple.com/newsroom/2020/11/apple-unleashes-m1/
[3]: https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment
[4]: https://brew.sh
[5]: https://www.unix.com/man-page/osx/1/arch/
[6]: https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment#3616845
  • Loading branch information
jonpryor committed Jan 22, 2021
1 parent 7cf4105 commit 77a6fd9
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 2 deletions.
13 changes: 13 additions & 0 deletions Documentation/building/unix/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Building Xamarin.Android requires:

* [Homebrew](#homebrew)
* [Latest Mono](#mono-sdk)
* [The Java Development Kit (JDK)](#jdk)
* [Autotools (`autoconf`, `automake`, etc.)](#autotools)
Expand All @@ -28,6 +29,18 @@ to provide install instructions to obtain the missing dependency, e.g.:

error : Could not find required program '7za'. Please run: brew install 'p7zip'.

<a name="homebrew" />

## Homebrew

[Homebrew](https://brew.sh) must be installed and available via `$PATH` in
order to provision xamarin-android.

When building on Apple Silicon (arm64) machines, use the **arch**(1) command to
allow Homebrew to be installed:

% arch -arch x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"


<a name="mono-sdk" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void Generate (Context context, StreamWriter sw)

WriteVariable ("export OS_NAME", context.OS.Type);
WriteVariable ("export OS_ARCH", context.OS.Architecture);
WriteVariable ("export OS_ARCH_TRANSLATED", context.OS.ProcessIsTranslated ? "true" : "false");
WriteVariable ("PRODUCT_VERSION", context.ProductVersion);
WriteVariable ("MONO_SOURCE_FULL_PATH", Configurables.Paths.MonoSourceFullPath);

Expand Down
23 changes: 23 additions & 0 deletions build-tools/xaprepare/xaprepare/OperatingSystems/MacOS.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Xamarin.Android.Prepare
{
Expand All @@ -26,6 +27,10 @@ brew doctor
public Version HomebrewVersion { get; private set; }
public bool HomebrewErrors { get; set; }

bool processIsTranslated;

public override bool ProcessIsTranslated => processIsTranslated;

public MacOS (Context context)
: base (context)
{
Expand All @@ -52,9 +57,21 @@ public MacOS (Context context)
Version = new Version (0, 0, 0);
}

processIsTranslated = GetProcessIsTranslated ();

Dependencies = new List<Program> ();
}

unsafe bool GetProcessIsTranslated ()
{
int ret = 0;
ulong size = sizeof (int);
if (NativeMethods.sysctlbyname ("sysctl.proc_translated", &ret, &size, null, 0) == -1) {
return false;
}
return ret != 0;
}

protected override void PopulateEnvironmentVariables ()
{
base.PopulateEnvironmentVariables ();
Expand Down Expand Up @@ -104,4 +121,10 @@ public override void ShowFinalNotices ()
Log.WarningLine (HomebrewErrorsAdvice, ConsoleColor.White, showSeverity: false);
}
};

partial class NativeMethods {
[DllImport ("c")]
internal static unsafe extern int sysctlbyname (
string name, void* oldp, ulong *oldlenp, void* newp, ulong newlen);
}
}
2 changes: 2 additions & 0 deletions build-tools/xaprepare/xaprepare/OperatingSystems/OS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ abstract class OS : AppObject
/// </summary>
public string HomebrewPrefix { get; set; } = String.Empty;

public virtual bool ProcessIsTranslated => false;

/// <summary>
/// File extension used for ZIP archives
/// </summary>
Expand Down
21 changes: 19 additions & 2 deletions build-tools/xaprepare/xaprepare/ToolRunners/BrewRunner.MacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ partial class BrewRunner : ToolRunner
static readonly char[] lineSplit = new [] { '\n' };

bool? needSudo;
bool? needArch;

protected override string DefaultToolExecutableName => GetDefaultExecutableName ();
protected override string ToolName => "Homebrew";
Expand All @@ -34,9 +35,15 @@ string GetDefaultExecutableName ()
throw new InvalidOperationException ($"BrewRunner does not suppport {Context.Instance.OS.Name}");

needSudo = os.HomebrewVersion != null && os.HomebrewVersion < sudoVersion;
needArch = os.ProcessIsTranslated;
}

return needSudo.Value ? "sudo" : BrewPath;
if (needSudo ?? false)
return "sudo";
if (needArch ?? false)
return "arch";

return BrewPath;
}

public async Task<bool> Tap (string tapName, bool echoOutput = true, bool echoError = true)
Expand Down Expand Up @@ -159,7 +166,17 @@ ProcessRunner GetBrewRunner (bool echoOutput, bool echoError, List<string> argum
{
ProcessRunner runner = CreateProcessRunner ();

if (needSudo.HasValue && needSudo.Value) {
if ((needSudo ?? false) && (needArch ?? false)) {
// So we run `sudo arch -arch x86_64 brew …`
runner.AddArgument ("arch");
}

if (needArch ?? false) {
runner.AddArgument ("-arch");
runner.AddArgument ("x86_64");
}

if ((needSudo ?? false) || (needArch ?? false)) {
runner.AddArgument (BrewPath);
}

Expand Down

0 comments on commit 77a6fd9

Please sign in to comment.