Support cross-compiling GN for other targets

This change extends the recipe used by builders with support for
cross-compiling GN to different platforms beyond the host one, and
sets up cross compilation for Linux as an example.

Change-Id: I0d9aa1a104b14842c875d1a8d9082b6949e47e14
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/3760
Reviewed-by: Scott Graham <scottmg@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Commit-Queue: Petr Hosek <phosek@google.com>
diff --git a/infra/recipe_modules/target/__init__.py b/infra/recipe_modules/target/__init__.py
new file mode 100644
index 0000000..1239fd2
--- /dev/null
+++ b/infra/recipe_modules/target/__init__.py
@@ -0,0 +1,7 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'recipe_engine/platform',
+]
diff --git a/infra/recipe_modules/target/api.py b/infra/recipe_modules/target/api.py
new file mode 100644
index 0000000..f128707
--- /dev/null
+++ b/infra/recipe_modules/target/api.py
@@ -0,0 +1,98 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from recipe_engine import recipe_api
+
+
+PLATFORM_TO_TRIPLE = {
+  'fuchsia-amd64': 'x86_64-fuchsia',
+  'fuchsia-arm64': 'aarch64-fuchsia',
+  'linux-amd64': 'x86_64-linux-gnu',
+  'linux-arm64': 'aarch64-linux-gnu',
+  'mac-amd64': 'x86_64-apple-darwin',
+  'mac-arm64': 'aarch64-apple-darwin',
+}
+PLATFORMS = PLATFORM_TO_TRIPLE.keys()
+
+
+class Target(object):
+
+  def __init__(self, api, os, arch):
+    self.m = api
+    self._os = os
+    self._arch = arch
+
+  @property
+  def is_win(self):
+    """Returns True iff the target platform is Windows."""
+    return self.os == 'windows'
+
+  @property
+  def is_mac(self):
+    """Returns True iff the target platform is macOS."""
+    return self.os == 'mac'
+
+  @property
+  def is_linux(self):
+    """Returns True iff the target platform is Linux."""
+    return self.os == 'linux'
+
+  @property
+  def is_host(self):
+    """Returns True iff the target platform is host."""
+    return self == self.m.host
+
+  @property
+  def os(self):
+    """Returns the target os name which will be in:
+      * windows
+      * mac
+      * linux
+    """
+    return self._os
+
+  @property
+  def arch(self):
+    """Returns the current CPU architecture."""
+    return self._arch
+
+  @property
+  def platform(self):
+    """Returns the target platform in the <os>-<arch> format."""
+    return '%s-%s' % (self.os, self.arch)
+
+  @property
+  def triple(self):
+    """Returns the target triple."""
+    return PLATFORM_TO_TRIPLE[self.platform]
+
+  def __str__(self):
+    return self.platform
+
+  def __eq__(self, other):
+    if isinstance(other, Target):
+      return self._os == other._os and self._arch == other._arch
+    return False
+
+  def __ne__(self, other):
+    return not self.__eq__(other)
+
+
+class TargetApi(recipe_api.RecipeApi):
+
+  def __call__(self, platform):
+    return Target(self, *platform.split('-', 2))
+
+  @property
+  def host(self):
+    return Target(self, self.m.platform.name.replace('win', 'windows'), {
+        'intel': {
+            32: '386',
+            64: 'amd64',
+        },
+        'arm': {
+            32: 'armv6',
+            64: 'arm64',
+        },
+    }[self.m.platform.arch][self.m.platform.bits])
diff --git a/infra/recipe_modules/target/examples/full.expected/linux.json b/infra/recipe_modules/target/examples/full.expected/linux.json
new file mode 100644
index 0000000..947f5d2
--- /dev/null
+++ b/infra/recipe_modules/target/examples/full.expected/linux.json
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.expected/mac.json b/infra/recipe_modules/target/examples/full.expected/mac.json
new file mode 100644
index 0000000..947f5d2
--- /dev/null
+++ b/infra/recipe_modules/target/examples/full.expected/mac.json
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.expected/win.json b/infra/recipe_modules/target/examples/full.expected/win.json
new file mode 100644
index 0000000..947f5d2
--- /dev/null
+++ b/infra/recipe_modules/target/examples/full.expected/win.json
@@ -0,0 +1,22 @@
+[
+  {
+    "cmd": [],
+    "name": "platform things",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@name@fuchsia@@@",
+      "@@@STEP_LOG_END@name@@@",
+      "@@@STEP_LOG_LINE@arch@arm64@@@",
+      "@@@STEP_LOG_END@arch@@@",
+      "@@@STEP_LOG_LINE@platform@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@platform@@@",
+      "@@@STEP_LOG_LINE@triple@aarch64-fuchsia@@@",
+      "@@@STEP_LOG_END@triple@@@",
+      "@@@STEP_LOG_LINE@string@fuchsia-arm64@@@",
+      "@@@STEP_LOG_END@string@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/recipe_modules/target/examples/full.py b/infra/recipe_modules/target/examples/full.py
new file mode 100644
index 0000000..c47c86a
--- /dev/null
+++ b/infra/recipe_modules/target/examples/full.py
@@ -0,0 +1,31 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+DEPS = [
+    'target',
+    'recipe_engine/platform',
+    'recipe_engine/properties',
+    'recipe_engine/step',
+]
+
+
+def RunSteps(api):
+  target = api.target('fuchsia-arm64')
+  assert not target.is_win
+  assert not target.is_linux
+  assert not target.is_mac
+  assert api.target.host.is_host
+  assert target != api.target.host
+  assert target != 'foo'
+  step_result = api.step('platform things', cmd=None)
+  step_result.presentation.logs['name'] = [target.os]
+  step_result.presentation.logs['arch'] = [target.arch]
+  step_result.presentation.logs['platform'] = [target.platform]
+  step_result.presentation.logs['triple'] = [target.triple]
+  step_result.presentation.logs['string'] = [str(target)]
+
+
+def GenTests(api):
+  for platform in ('linux', 'mac', 'win'):
+    yield api.test(platform) + api.platform.name(platform)